@open-mercato/shared 0.6.7 → 0.6.8-canary.320.1.bf889f49e4
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +62 -6
- package/build.mjs +2 -4
- package/dist/lib/ai/safety-identifier.js +32 -0
- package/dist/lib/ai/safety-identifier.js.map +7 -0
- package/dist/lib/auth/organizationScope.js +34 -0
- package/dist/lib/auth/organizationScope.js.map +7 -0
- package/dist/lib/auth/server.js +15 -2
- package/dist/lib/auth/server.js.map +2 -2
- package/dist/lib/bootstrap/clientOnlyModules.js +55 -0
- package/dist/lib/bootstrap/clientOnlyModules.js.map +7 -0
- package/dist/lib/bootstrap/dynamicLoader.js +190 -70
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/bootstrap/factory.js +18 -13
- package/dist/lib/bootstrap/factory.js.map +2 -2
- package/dist/lib/commands/command-bus.js +9 -3
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/command-interceptor-runner.js +9 -4
- package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
- package/dist/lib/commands/errors.js +25 -4
- package/dist/lib/commands/errors.js.map +2 -2
- package/dist/lib/commands/index.js +7 -1
- package/dist/lib/commands/index.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +2 -11
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +104 -23
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/interceptor-runner.js +2 -2
- package/dist/lib/crud/interceptor-runner.js.map +2 -2
- package/dist/lib/crud/mutation-guard-registry.js +2 -2
- package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
- package/dist/lib/crud/types.js +4 -0
- package/dist/lib/crud/types.js.map +3 -3
- package/dist/lib/data/consistency.js +19 -0
- package/dist/lib/data/consistency.js.map +7 -0
- package/dist/lib/data/engine.js +37 -11
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/entityMetadata.js +26 -0
- package/dist/lib/db/entityMetadata.js.map +7 -0
- package/dist/lib/db/pg-errors.js +42 -0
- package/dist/lib/db/pg-errors.js.map +2 -2
- package/dist/lib/di/container.js +48 -6
- package/dist/lib/di/container.js.map +2 -2
- package/dist/lib/email/send.js +56 -0
- package/dist/lib/email/send.js.map +2 -2
- package/dist/lib/encryption/find.js +22 -0
- package/dist/lib/encryption/find.js.map +2 -2
- package/dist/lib/encryption/likeFilterWarning.js +66 -0
- package/dist/lib/encryption/likeFilterWarning.js.map +7 -0
- package/dist/lib/encryption/subscriber.js +2 -10
- package/dist/lib/encryption/subscriber.js.map +2 -2
- package/dist/lib/encryption/tenantDataEncryptionService.js +52 -17
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
- package/dist/lib/i18n/config.js +1 -1
- package/dist/lib/i18n/config.js.map +2 -2
- package/dist/lib/i18n/dictionary-cache.js +5 -0
- package/dist/lib/i18n/dictionary-cache.js.map +2 -2
- package/dist/lib/i18n/server.js +4 -4
- package/dist/lib/i18n/server.js.map +3 -3
- package/dist/lib/logger/extension.js +29 -0
- package/dist/lib/logger/extension.js.map +7 -0
- package/dist/lib/logger/index.js +47 -3
- package/dist/lib/logger/index.js.map +3 -3
- package/dist/lib/modules/registry.js +36 -6
- package/dist/lib/modules/registry.js.map +2 -2
- package/dist/lib/modules/surfaceFingerprint.js +47 -0
- package/dist/lib/modules/surfaceFingerprint.js.map +7 -0
- package/dist/lib/query/ciphertext-search-warning.js +51 -0
- package/dist/lib/query/ciphertext-search-warning.js.map +7 -0
- package/dist/lib/query/engine.js +60 -43
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/query/types.js.map +1 -1
- package/dist/lib/redis/connection.js +7 -2
- package/dist/lib/redis/connection.js.map +2 -2
- package/dist/lib/search/auto-indexing.js +14 -0
- package/dist/lib/search/auto-indexing.js.map +7 -0
- package/dist/lib/search/availability.js +107 -0
- package/dist/lib/search/availability.js.map +7 -0
- package/dist/lib/search/config.js +61 -2
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/search/tokenLookup.js +46 -0
- package/dist/lib/search/tokenLookup.js.map +7 -0
- package/dist/lib/search/tokenize.js +23 -11
- package/dist/lib/search/tokenize.js.map +2 -2
- package/dist/lib/telemetry/runtime.js +35 -0
- package/dist/lib/telemetry/runtime.js.map +7 -0
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/webhooks/body.js +57 -0
- package/dist/lib/webhooks/body.js.map +7 -0
- package/dist/lib/webhooks/inbound-types.js +1 -0
- package/dist/lib/webhooks/inbound-types.js.map +7 -0
- package/dist/lib/webhooks/index.js +13 -1
- package/dist/lib/webhooks/index.js.map +2 -2
- package/dist/lib/webhooks/verify.js +12 -7
- package/dist/lib/webhooks/verify.js.map +2 -2
- package/dist/modules/analytics.js.map +2 -2
- package/dist/modules/overrides.js +50 -1
- package/dist/modules/overrides.js.map +2 -2
- package/dist/modules/payment_gateways/types.js +8 -1
- package/dist/modules/payment_gateways/types.js.map +2 -2
- package/dist/modules/registry.js +1 -0
- package/dist/modules/registry.js.map +2 -2
- package/dist/modules/widgets/extension-points.js +96 -0
- package/dist/modules/widgets/extension-points.js.map +7 -0
- package/dist/security/enabledModulesRegistry.js +56 -11
- package/dist/security/enabledModulesRegistry.js.map +2 -2
- package/dist/security/featurePolicy.js +62 -0
- package/dist/security/featurePolicy.js.map +7 -0
- package/jest.config.cjs +20 -10
- package/package.json +19 -9
- package/scripts/versionSource.cjs +65 -0
- package/src/lib/__tests__/versionSource.test.ts +64 -0
- package/src/lib/ai/__tests__/llm-provider-contract.test.ts +59 -0
- package/src/lib/ai/__tests__/safety-identifier.test.ts +71 -0
- package/src/lib/ai/llm-provider.ts +33 -0
- package/src/lib/ai/safety-identifier.ts +73 -0
- package/src/lib/auth/__tests__/organizationScope.test.ts +107 -0
- package/src/lib/auth/__tests__/server.test.ts +23 -0
- package/src/lib/auth/organizationScope.ts +65 -0
- package/src/lib/auth/server.ts +23 -2
- package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +189 -0
- package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +98 -0
- package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +211 -0
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +63 -8
- package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +256 -0
- package/src/lib/bootstrap/__tests__/dynamicLoader.test.ts +126 -0
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +21 -1
- package/src/lib/bootstrap/__tests__/factory.test.ts +78 -0
- package/src/lib/bootstrap/clientOnlyModules.ts +85 -0
- package/src/lib/bootstrap/dynamicLoader.ts +346 -96
- package/src/lib/bootstrap/factory.ts +21 -15
- package/src/lib/bootstrap/types.ts +6 -0
- package/src/lib/commands/__tests__/command-bus.test.ts +63 -0
- package/src/lib/commands/__tests__/command-interceptor-error.test.ts +91 -1
- package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +101 -0
- package/src/lib/commands/command-bus.ts +9 -3
- package/src/lib/commands/command-interceptor-runner.ts +27 -6
- package/src/lib/commands/command-interceptor.ts +8 -0
- package/src/lib/commands/errors.ts +67 -2
- package/src/lib/commands/index.ts +7 -1
- package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +171 -0
- package/src/lib/crud/__tests__/crud-factory.test.ts +564 -0
- package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +26 -0
- package/src/lib/crud/enricher-runner.ts +2 -11
- package/src/lib/crud/factory.ts +157 -22
- package/src/lib/crud/interceptor-runner.ts +2 -2
- package/src/lib/crud/mutation-guard-registry.ts +2 -2
- package/src/lib/crud/types.ts +3 -0
- package/src/lib/data/__tests__/consistency.test.ts +38 -0
- package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +18 -3
- package/src/lib/data/consistency.ts +17 -0
- package/src/lib/data/engine.ts +50 -16
- package/src/lib/db/__tests__/entityMetadata.test.ts +38 -0
- package/src/lib/db/__tests__/pg-errors.test.ts +48 -0
- package/src/lib/db/entityMetadata.ts +43 -0
- package/src/lib/db/pg-errors.ts +58 -0
- package/src/lib/di/__tests__/container-app-di-absent.test.ts +155 -0
- package/src/lib/di/__tests__/container-app-di.test.ts +173 -0
- package/src/lib/di/__tests__/registrar-error-log.test.ts +154 -0
- package/src/lib/di/container.ts +72 -8
- package/src/lib/email/__tests__/send.test.ts +37 -1
- package/src/lib/email/send.ts +79 -0
- package/src/lib/encryption/__tests__/likeFilterWarning.test.ts +167 -0
- package/src/lib/encryption/__tests__/subscriber.test.ts +54 -2
- package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +58 -0
- package/src/lib/encryption/find.ts +22 -0
- package/src/lib/encryption/likeFilterWarning.ts +102 -0
- package/src/lib/encryption/subscriber.ts +2 -14
- package/src/lib/encryption/tenantDataEncryptionService.ts +64 -18
- package/src/lib/i18n/__tests__/config.test.ts +8 -0
- package/src/lib/i18n/__tests__/dictionary-cache.test.ts +23 -0
- package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +30 -0
- package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +29 -0
- package/src/lib/i18n/config.ts +2 -2
- package/src/lib/i18n/dictionary-cache.ts +5 -0
- package/src/lib/i18n/server.ts +6 -2
- package/src/lib/logger/__tests__/logger.test.ts +77 -0
- package/src/lib/logger/extension.ts +59 -0
- package/src/lib/logger/index.ts +59 -1
- package/src/lib/modules/__tests__/registry.test.ts +26 -0
- package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +122 -0
- package/src/lib/modules/registry.ts +46 -7
- package/src/lib/modules/surfaceFingerprint.ts +87 -0
- package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +178 -0
- package/src/lib/query/__tests__/engine.scope-and-or.test.ts +67 -0
- package/src/lib/query/__tests__/engine.test.ts +213 -13
- package/src/lib/query/ciphertext-search-warning.ts +106 -0
- package/src/lib/query/engine.ts +102 -61
- package/src/lib/query/types.ts +4 -5
- package/src/lib/redis/__tests__/connection.test.ts +16 -0
- package/src/lib/redis/connection.ts +10 -7
- package/src/lib/search/__tests__/availability.test.ts +211 -0
- package/src/lib/search/__tests__/config.test.ts +177 -0
- package/src/lib/search/__tests__/tokenLookup.test.ts +206 -0
- package/src/lib/search/__tests__/tokenize.test.ts +49 -0
- package/src/lib/search/auto-indexing.ts +22 -0
- package/src/lib/search/availability.ts +232 -0
- package/src/lib/search/config.ts +106 -8
- package/src/lib/search/tokenLookup.ts +133 -0
- package/src/lib/search/tokenize.ts +33 -11
- package/src/lib/telemetry/runtime.ts +72 -0
- package/src/lib/webhooks/__tests__/body.test.ts +95 -0
- package/src/lib/webhooks/__tests__/verify.test.ts +20 -1
- package/src/lib/webhooks/body.ts +70 -0
- package/src/lib/webhooks/inbound-types.ts +125 -0
- package/src/lib/webhooks/index.ts +19 -1
- package/src/lib/webhooks/verify.ts +17 -9
- package/src/modules/__tests__/nav-group-order-override.test.ts +183 -0
- package/src/modules/__tests__/registry.test.ts +10 -2
- package/src/modules/analytics.ts +7 -0
- package/src/modules/customer-auth.ts +1 -0
- package/src/modules/encryption.ts +3 -0
- package/src/modules/entities.ts +15 -0
- package/src/modules/navigation/backendChrome.ts +15 -0
- package/src/modules/overrides.ts +103 -0
- package/src/modules/payment_gateways/__tests__/types.test.ts +37 -0
- package/src/modules/payment_gateways/types.ts +41 -0
- package/src/modules/registry.ts +3 -0
- package/src/modules/widgets/__tests__/extension-points.test.ts +101 -0
- package/src/modules/widgets/extension-points.ts +524 -0
- package/src/security/__tests__/featurePolicy.test.ts +166 -0
- package/src/security/enabledModulesRegistry.ts +64 -12
- package/src/security/featurePolicy.ts +89 -0
|
@@ -1,16 +1,137 @@
|
|
|
1
1
|
import type { BootstrapData } from './types'
|
|
2
|
+
import type { AppDiRegistrar } from '../di/container'
|
|
2
3
|
import { findAppRoot, type AppRoot } from './appResolver'
|
|
3
4
|
import { registerEntityIds } from '../encryption/entityIds'
|
|
5
|
+
import { createLogger } from '../logger'
|
|
4
6
|
import {
|
|
5
7
|
ensureMikroOrmV7GeneratedCacheCompatibility,
|
|
6
8
|
recoverMikroOrmV7GeneratedCacheFromImportError,
|
|
7
9
|
} from './generatedCacheRecovery'
|
|
10
|
+
import { CLIENT_ONLY_STUB_NAMESPACE, createClientOnlyStubPlugin } from './clientOnlyModules'
|
|
8
11
|
import path from 'node:path'
|
|
9
12
|
import fs from 'node:fs'
|
|
10
13
|
import crypto from 'node:crypto'
|
|
11
14
|
import { createRequire } from 'node:module'
|
|
12
15
|
import { pathToFileURL } from 'node:url'
|
|
13
16
|
|
|
17
|
+
let activeBootstrapLoads = 0
|
|
18
|
+
let esbuildRuntime: typeof import('esbuild') | null = null
|
|
19
|
+
let esbuildStopPromise: Promise<void> | null = null
|
|
20
|
+
|
|
21
|
+
const logger = createLogger('shared').child({ component: 'bootstrap' })
|
|
22
|
+
|
|
23
|
+
async function getEsbuildRuntime(): Promise<typeof import('esbuild')> {
|
|
24
|
+
if (esbuildStopPromise) await esbuildStopPromise
|
|
25
|
+
if (esbuildRuntime) return esbuildRuntime
|
|
26
|
+
|
|
27
|
+
const loadedRuntime = await import('esbuild')
|
|
28
|
+
esbuildRuntime ??= loadedRuntime
|
|
29
|
+
return esbuildRuntime
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function withEsbuildLifecycle<T>(load: () => Promise<T>): Promise<T> {
|
|
33
|
+
activeBootstrapLoads += 1
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return await load()
|
|
37
|
+
} finally {
|
|
38
|
+
activeBootstrapLoads -= 1
|
|
39
|
+
if (activeBootstrapLoads === 0 && esbuildRuntime) {
|
|
40
|
+
// esbuild keeps a helper process alive after build(). Bootstrap compilation
|
|
41
|
+
// is a bounded phase, so release it once every concurrent loader is done.
|
|
42
|
+
// A later build() call transparently starts a fresh helper process.
|
|
43
|
+
const runtimeToStop = esbuildRuntime
|
|
44
|
+
esbuildRuntime = null
|
|
45
|
+
const stopPromise = runtimeToStop.stop().catch((err) => {
|
|
46
|
+
logger.warn('Failed to stop the bootstrap compiler service', { err })
|
|
47
|
+
})
|
|
48
|
+
esbuildStopPromise = stopPromise
|
|
49
|
+
try {
|
|
50
|
+
await stopPromise
|
|
51
|
+
} finally {
|
|
52
|
+
if (esbuildStopPromise === stopPromise) esbuildStopPromise = null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Thrown when an expected generated source file is absent.
|
|
60
|
+
*
|
|
61
|
+
* Optional registries treat this as the supported compatibility case (an app
|
|
62
|
+
* that never generated the file), which is what makes it distinguishable from
|
|
63
|
+
* a file that exists but fails to compile or import.
|
|
64
|
+
*/
|
|
65
|
+
class GeneratedFileNotFoundError extends Error {
|
|
66
|
+
readonly filePath: string
|
|
67
|
+
|
|
68
|
+
constructor(filePath: string) {
|
|
69
|
+
super(`Generated file not found: ${filePath}`)
|
|
70
|
+
this.name = 'GeneratedFileNotFoundError'
|
|
71
|
+
this.filePath = filePath
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* esbuild plugins for the CLI bundle, in resolution order. The client-only stub must come
|
|
77
|
+
* first so it wins over the alias and external plugins for `*.client` dynamic imports.
|
|
78
|
+
*
|
|
79
|
+
* Exported so the wiring itself is testable: a test that only exercises
|
|
80
|
+
* `createClientOnlyStubPlugin` in isolation stays green if the plugin is dropped from this
|
|
81
|
+
* list, which would silently reintroduce #4623.
|
|
82
|
+
*/
|
|
83
|
+
export function createCliBundlePlugins(appRoot: string): import('esbuild').Plugin[] {
|
|
84
|
+
// Plugin to resolve the @/ alias the way the app tsconfig maps it:
|
|
85
|
+
// `@/.mercato/*` to the app root, every other `@/*` to the app's src/ directory.
|
|
86
|
+
const aliasPlugin: import('esbuild').Plugin = {
|
|
87
|
+
name: 'alias-resolver',
|
|
88
|
+
setup(build) {
|
|
89
|
+
build.onResolve({ filter: /^@\// }, (args) => {
|
|
90
|
+
const rest = args.path.slice('@/'.length)
|
|
91
|
+
const bases = rest.startsWith('.mercato/')
|
|
92
|
+
? [path.join(appRoot, rest)]
|
|
93
|
+
: [path.join(appRoot, 'src', rest), path.join(appRoot, rest)]
|
|
94
|
+
for (const base of bases) {
|
|
95
|
+
if (fs.existsSync(base) && fs.statSync(base).isFile()) {
|
|
96
|
+
return { path: base }
|
|
97
|
+
}
|
|
98
|
+
for (const suffix of ['.ts', '.tsx', '/index.ts', '/index.tsx']) {
|
|
99
|
+
if (fs.existsSync(base + suffix)) {
|
|
100
|
+
return { path: base + suffix }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Nothing matched — hand esbuild the literal mapping so it reports the
|
|
105
|
+
// missing file against the path the app author actually wrote.
|
|
106
|
+
return { path: path.join(appRoot, rest) }
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Plugin to mark non-JSON package imports as external
|
|
112
|
+
const externalNonJsonPlugin: import('esbuild').Plugin = {
|
|
113
|
+
name: 'external-non-json',
|
|
114
|
+
setup(build) {
|
|
115
|
+
// Mark all package imports as external EXCEPT JSON files
|
|
116
|
+
// Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)
|
|
117
|
+
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
118
|
+
// Skip Windows absolute paths (e.g., C:\...) - they're local files, not packages
|
|
119
|
+
if (/^[a-zA-Z]:/.test(args.path)) {
|
|
120
|
+
return null // Let esbuild handle it
|
|
121
|
+
}
|
|
122
|
+
// If it's a JSON file, let esbuild bundle it
|
|
123
|
+
if (args.path.endsWith('.json')) {
|
|
124
|
+
return null // Let esbuild handle it
|
|
125
|
+
}
|
|
126
|
+
// Otherwise mark as external
|
|
127
|
+
return { path: args.path, external: true }
|
|
128
|
+
})
|
|
129
|
+
},
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin]
|
|
133
|
+
}
|
|
134
|
+
|
|
14
135
|
const DYNAMIC_LOADER_CACHE_VERSION = 4
|
|
15
136
|
|
|
16
137
|
type DynamicLoaderCacheMetadata = {
|
|
@@ -167,6 +288,7 @@ function collectDependencyHashes(
|
|
|
167
288
|
): Record<string, string> {
|
|
168
289
|
return Object.fromEntries(
|
|
169
290
|
Object.keys(inputs)
|
|
291
|
+
.filter((inputPath) => !inputPath.startsWith(`${CLIENT_ONLY_STUB_NAMESPACE}:`))
|
|
170
292
|
.map((inputPath) => {
|
|
171
293
|
const absolutePath = path.isAbsolute(inputPath)
|
|
172
294
|
? inputPath
|
|
@@ -222,21 +344,74 @@ function cacheIsValid(
|
|
|
222
344
|
}
|
|
223
345
|
|
|
224
346
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
347
|
+
* Options for `compileAndImport`.
|
|
348
|
+
*
|
|
349
|
+
* Both paths default to the generated-registry layout (`<appRoot>/.mercato/generated/<file>.ts`
|
|
350
|
+
* compiled to a `.mjs` sibling). Sources that live elsewhere in the app — `src/di.ts` — MUST pass
|
|
351
|
+
* both explicitly: the default app root is derived by walking three directories up from the source,
|
|
352
|
+
* which only holds inside `.mercato/generated`.
|
|
353
|
+
*/
|
|
354
|
+
type CompileAndImportOptions = {
|
|
355
|
+
appRoot?: string
|
|
356
|
+
outFile?: string
|
|
357
|
+
allowRecovery?: boolean
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Options for `compileAppSourceFile`.
|
|
362
|
+
*
|
|
363
|
+
* `appRoot` anchors the tsconfig, the `@/` alias resolution and the dependency
|
|
364
|
+
* cache; `outFile` is the absolute path of the artifact to write. `format`
|
|
365
|
+
* selects the module system of that artifact — `'cjs'` exists for the Jest
|
|
366
|
+
* runtime, which cannot `import()` an ESM sibling.
|
|
367
|
+
*/
|
|
368
|
+
export type CompileAppSourceOptions = {
|
|
369
|
+
appRoot: string
|
|
370
|
+
outFile: string
|
|
371
|
+
format?: 'esm' | 'cjs'
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Compile one app-owned TypeScript source and its relative import graph into a
|
|
376
|
+
* single JavaScript artifact, leaving every package import external.
|
|
377
|
+
*
|
|
378
|
+
* This is the only supported way to load app source (`apps/<app>/src/**`,
|
|
379
|
+
* `.mercato/generated/**`) from a plain Node process. Those files are never
|
|
380
|
+
* compiled to `dist`, and Node's own type stripping cannot load them: it
|
|
381
|
+
* requires explicit file extensions on relative specifiers and rejects the
|
|
382
|
+
* decorator and enum syntax the entities and DI files use.
|
|
383
|
+
*
|
|
384
|
+
* The artifact is cached against the content of the entry, its whole bundled
|
|
385
|
+
* dependency graph, and the tsconfig chain, so an edit anywhere in the graph
|
|
386
|
+
* invalidates it.
|
|
387
|
+
*
|
|
388
|
+
* The build runs inside the shared esbuild lifecycle. Callers outside a
|
|
389
|
+
* bootstrap load — the generated-registry loader compiling an `@app` module —
|
|
390
|
+
* would otherwise hold a build on a service another scope is entitled to
|
|
391
|
+
* `stop()`, and would leave the helper process running afterwards. Nesting is
|
|
392
|
+
* safe: the scope only releases the service when the last participant exits.
|
|
228
393
|
*/
|
|
229
|
-
async function
|
|
230
|
-
|
|
231
|
-
|
|
394
|
+
export async function compileAppSourceFile(
|
|
395
|
+
tsPath: string,
|
|
396
|
+
options: CompileAppSourceOptions,
|
|
397
|
+
): Promise<string> {
|
|
398
|
+
return withEsbuildLifecycle(() => compileAppSourceFileWithActiveEsbuild(tsPath, options))
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function compileAppSourceFileWithActiveEsbuild(
|
|
402
|
+
tsPath: string,
|
|
403
|
+
options: CompileAppSourceOptions,
|
|
404
|
+
): Promise<string> {
|
|
405
|
+
const { appRoot, outFile } = options
|
|
406
|
+
const format = options.format ?? 'esm'
|
|
232
407
|
const appTsconfig = path.join(appRoot, 'tsconfig.json')
|
|
233
|
-
const metadataPath = cacheMetadataPath(
|
|
408
|
+
const metadataPath = cacheMetadataPath(outFile)
|
|
234
409
|
|
|
235
410
|
const tsExists = fs.existsSync(tsPath)
|
|
236
411
|
const tsconfigExists = fs.existsSync(appTsconfig)
|
|
237
412
|
|
|
238
413
|
if (!tsExists) {
|
|
239
|
-
throw new
|
|
414
|
+
throw new GeneratedFileNotFoundError(tsPath)
|
|
240
415
|
}
|
|
241
416
|
if (!tsconfigExists) {
|
|
242
417
|
throw new Error(`App TypeScript config not found: ${appTsconfig}`)
|
|
@@ -244,79 +419,59 @@ async function compileAndImport(tsPath: string, allowRecovery: boolean = true):
|
|
|
244
419
|
|
|
245
420
|
const tsconfigPaths = collectTsconfigPaths(appTsconfig)
|
|
246
421
|
const expectedInputHash = cacheInputHash(tsPath, appRoot, tsconfigPaths)
|
|
247
|
-
const needsCompile = !cacheIsValid(appRoot, jsPath, metadataPath, expectedInputHash)
|
|
248
|
-
|
|
249
|
-
if (needsCompile) {
|
|
250
|
-
// Dynamically import esbuild only when needed
|
|
251
|
-
const esbuild = await import('esbuild')
|
|
252
|
-
|
|
253
|
-
// Plugin to resolve @/ alias to app root (works for @app modules)
|
|
254
|
-
const aliasPlugin: import('esbuild').Plugin = {
|
|
255
|
-
name: 'alias-resolver',
|
|
256
|
-
setup(build) {
|
|
257
|
-
// Resolve @/ alias to app root
|
|
258
|
-
build.onResolve({ filter: /^@\// }, (args) => {
|
|
259
|
-
const resolved = path.join(appRoot, args.path.slice(2))
|
|
260
|
-
// Try with .ts extension if base path doesn't exist
|
|
261
|
-
if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.ts')) {
|
|
262
|
-
return { path: resolved + '.ts' }
|
|
263
|
-
}
|
|
264
|
-
// Also check for /index.ts if it's a directory
|
|
265
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory() && fs.existsSync(path.join(resolved, 'index.ts'))) {
|
|
266
|
-
return { path: path.join(resolved, 'index.ts') }
|
|
267
|
-
}
|
|
268
|
-
return { path: resolved }
|
|
269
|
-
})
|
|
270
|
-
},
|
|
271
|
-
}
|
|
272
422
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
setup(build) {
|
|
277
|
-
// Mark all package imports as external EXCEPT JSON files
|
|
278
|
-
// Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)
|
|
279
|
-
build.onResolve({ filter: /^[^./]/ }, (args) => {
|
|
280
|
-
// Skip Windows absolute paths (e.g., C:\...) - they're local files, not packages
|
|
281
|
-
if (/^[a-zA-Z]:/.test(args.path)) {
|
|
282
|
-
return null // Let esbuild handle it
|
|
283
|
-
}
|
|
284
|
-
// If it's a JSON file, let esbuild bundle it
|
|
285
|
-
if (args.path.endsWith('.json')) {
|
|
286
|
-
return null // Let esbuild handle it
|
|
287
|
-
}
|
|
288
|
-
// Otherwise mark as external
|
|
289
|
-
return { path: args.path, external: true }
|
|
290
|
-
})
|
|
291
|
-
},
|
|
292
|
-
}
|
|
423
|
+
if (cacheIsValid(appRoot, outFile, metadataPath, expectedInputHash)) {
|
|
424
|
+
return outFile
|
|
425
|
+
}
|
|
293
426
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
427
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true })
|
|
428
|
+
// Dynamically import esbuild only when needed
|
|
429
|
+
const esbuild = await getEsbuildRuntime()
|
|
430
|
+
|
|
431
|
+
// Use esbuild.build with bundling to handle JSON imports
|
|
432
|
+
const result = await esbuild.build({
|
|
433
|
+
entryPoints: [tsPath],
|
|
434
|
+
outfile: outFile,
|
|
435
|
+
absWorkingDir: appRoot,
|
|
436
|
+
bundle: true,
|
|
437
|
+
metafile: true,
|
|
438
|
+
format,
|
|
439
|
+
platform: 'node',
|
|
440
|
+
target: 'node18',
|
|
441
|
+
tsconfig: appTsconfig,
|
|
442
|
+
plugins: createCliBundlePlugins(appRoot),
|
|
443
|
+
// Allow JSON imports
|
|
444
|
+
loader: { '.json': 'json' },
|
|
445
|
+
})
|
|
446
|
+
const metadata: DynamicLoaderCacheMetadata = {
|
|
447
|
+
version: DYNAMIC_LOADER_CACHE_VERSION,
|
|
448
|
+
inputHash: expectedInputHash,
|
|
449
|
+
outputHash: contentHash(fs.readFileSync(outFile)),
|
|
450
|
+
dependencies: {
|
|
451
|
+
...collectDependencyHashes(appRoot, result.metafile.inputs),
|
|
452
|
+
...hashFilesRelativeTo(appRoot, tsconfigPaths),
|
|
453
|
+
},
|
|
319
454
|
}
|
|
455
|
+
fs.writeFileSync(metadataPath, JSON.stringify(metadata))
|
|
456
|
+
|
|
457
|
+
return outFile
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Compile a TypeScript file to JavaScript using esbuild bundler.
|
|
462
|
+
* This bundles the file and all its dependencies, handling JSON imports properly.
|
|
463
|
+
* The compiled file is written next to the source file with a .mjs extension unless
|
|
464
|
+
* `outFile` says otherwise.
|
|
465
|
+
*/
|
|
466
|
+
async function compileAndImport(
|
|
467
|
+
tsPath: string,
|
|
468
|
+
options: CompileAndImportOptions = {},
|
|
469
|
+
): Promise<Record<string, unknown>> {
|
|
470
|
+
const allowRecovery = options.allowRecovery ?? true
|
|
471
|
+
const jsPath = options.outFile ?? tsPath.replace(/\.ts$/, '.mjs')
|
|
472
|
+
const appRoot = options.appRoot ?? path.dirname(path.dirname(path.dirname(tsPath)))
|
|
473
|
+
|
|
474
|
+
await compileAppSourceFile(tsPath, { appRoot, outFile: jsPath })
|
|
320
475
|
|
|
321
476
|
// Import the compiled JavaScript
|
|
322
477
|
try {
|
|
@@ -333,25 +488,45 @@ async function compileAndImport(tsPath: string, allowRecovery: boolean = true):
|
|
|
333
488
|
throw error
|
|
334
489
|
}
|
|
335
490
|
|
|
336
|
-
return compileAndImport(tsPath, false)
|
|
491
|
+
return compileAndImport(tsPath, { ...options, allowRecovery: false })
|
|
337
492
|
}
|
|
338
493
|
}
|
|
339
494
|
|
|
340
495
|
|
|
341
496
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* IMPORTANT: This only works in unbundled contexts (CLI, tsx).
|
|
345
|
-
* Do NOT use this in Next.js bundled code - use static imports instead.
|
|
346
|
-
*
|
|
347
|
-
* For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.
|
|
348
|
-
* CLI commands are discovered separately via the CLI module system.
|
|
497
|
+
* Load a generated registry that older apps may not have generated yet.
|
|
349
498
|
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
499
|
+
* An absent source file is the supported compatibility case and resolves to
|
|
500
|
+
* `fallback` quietly. Any other failure — a compile error, a broken import, a
|
|
501
|
+
* runtime throw at module scope — still resolves to `fallback` so bootstrap
|
|
502
|
+
* keeps working, but is reported at error level: a registry that silently
|
|
503
|
+
* degrades to nothing is exactly how command interceptors stopped applying in
|
|
504
|
+
* worker/CLI processes (#4327, #4491).
|
|
353
505
|
*/
|
|
354
|
-
|
|
506
|
+
async function loadOptionalGeneratedModule(
|
|
507
|
+
tsPath: string,
|
|
508
|
+
fallback: Record<string, unknown>,
|
|
509
|
+
): Promise<Record<string, unknown>> {
|
|
510
|
+
try {
|
|
511
|
+
return await compileAndImport(tsPath)
|
|
512
|
+
} catch (error) {
|
|
513
|
+
if (error instanceof GeneratedFileNotFoundError) {
|
|
514
|
+
logger.debug('Optional generated registry not present, using empty fallback', {
|
|
515
|
+
file: path.basename(tsPath),
|
|
516
|
+
})
|
|
517
|
+
return fallback
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
logger.error('Failed to load generated registry, continuing without its entries', {
|
|
521
|
+
file: path.basename(tsPath),
|
|
522
|
+
filePath: tsPath,
|
|
523
|
+
err: error,
|
|
524
|
+
})
|
|
525
|
+
return fallback
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function resolveAppRootOrThrow(appRoot?: string): AppRoot {
|
|
355
530
|
const resolved: AppRoot | null = appRoot
|
|
356
531
|
? {
|
|
357
532
|
generatedDir: path.join(appRoot, '.mercato', 'generated'),
|
|
@@ -368,6 +543,68 @@ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData
|
|
|
368
543
|
)
|
|
369
544
|
}
|
|
370
545
|
|
|
546
|
+
return resolved
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Load the app-level DI registrar (`src/di.ts`) for the dynamic bootstrap path.
|
|
551
|
+
*
|
|
552
|
+
* The Next.js runtime imports `@/di` statically from its own `src/bootstrap.ts` and hands the
|
|
553
|
+
* registrar to `createBootstrap`. Worker, scheduler and CLI processes bootstrap through
|
|
554
|
+
* `bootstrapFromAppRoot` instead, where the `@/` alias does not exist — so without this the app's
|
|
555
|
+
* DI registrations silently never ran there, and every request container paid a failed
|
|
556
|
+
* `import('@/di')` resolution (the compatibility fallback in `lib/di/container.ts`).
|
|
557
|
+
*
|
|
558
|
+
* An absent `src/di.ts` is the supported case and resolves to `null` quietly. A file that exists
|
|
559
|
+
* but cannot be compiled, imported, or does not export `register` is reported at error level and
|
|
560
|
+
* still resolves to `null`, so a broken app DI module degrades the same way a broken generated
|
|
561
|
+
* registry does (#4327, #4491) instead of taking the whole process down.
|
|
562
|
+
*/
|
|
563
|
+
async function loadAppDiRegistrar(appDir: string): Promise<AppDiRegistrar | null> {
|
|
564
|
+
const tsPath = path.join(appDir, 'src', 'di.ts')
|
|
565
|
+
if (!fs.existsSync(tsPath)) {
|
|
566
|
+
logger.debug('App-level DI module not present, skipping its registrations', { filePath: tsPath })
|
|
567
|
+
return null
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
const appDiModule = await compileAndImport(tsPath, {
|
|
572
|
+
appRoot: appDir,
|
|
573
|
+
outFile: path.join(appDir, '.mercato', 'generated', 'app-di.compiled.mjs'),
|
|
574
|
+
})
|
|
575
|
+
const register = appDiModule.register
|
|
576
|
+
if (typeof register !== 'function') {
|
|
577
|
+
logger.error('App-level DI module exports no register(); its registrations are skipped', {
|
|
578
|
+
filePath: tsPath,
|
|
579
|
+
})
|
|
580
|
+
return null
|
|
581
|
+
}
|
|
582
|
+
return register as AppDiRegistrar
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logger.error('Failed to load the app-level DI module; its registrations are skipped', {
|
|
585
|
+
filePath: tsPath,
|
|
586
|
+
err: error,
|
|
587
|
+
})
|
|
588
|
+
return null
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Dynamically load bootstrap data from a resolved app directory.
|
|
594
|
+
*
|
|
595
|
+
* IMPORTANT: This only works in unbundled contexts (CLI, tsx).
|
|
596
|
+
* Do NOT use this in Next.js bundled code - use static imports instead.
|
|
597
|
+
*
|
|
598
|
+
* For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.
|
|
599
|
+
* CLI commands are discovered separately via the CLI module system.
|
|
600
|
+
*
|
|
601
|
+
* @param appRoot - Optional explicit app root path. If not provided, will search from cwd.
|
|
602
|
+
* @returns The loaded bootstrap data
|
|
603
|
+
* @throws Error if app root cannot be found or generated files are missing
|
|
604
|
+
*/
|
|
605
|
+
async function loadBootstrapDataWithActiveEsbuild(appRoot?: string): Promise<BootstrapData> {
|
|
606
|
+
const resolved = resolveAppRootOrThrow(appRoot)
|
|
607
|
+
|
|
371
608
|
const { generatedDir } = resolved
|
|
372
609
|
|
|
373
610
|
ensureMikroOrmV7GeneratedCacheCompatibility(resolved.appDir)
|
|
@@ -392,10 +629,12 @@ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData
|
|
|
392
629
|
compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),
|
|
393
630
|
compileAndImport(path.join(generatedDir, 'entities.generated.ts')),
|
|
394
631
|
compileAndImport(path.join(generatedDir, 'di.generated.ts')),
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
632
|
+
loadOptionalGeneratedModule(path.join(generatedDir, 'search.generated.ts'), { searchModuleConfigs: [] }),
|
|
633
|
+
loadOptionalGeneratedModule(path.join(generatedDir, 'command-loaders.generated.ts'), { commandLoaderEntries: [] }),
|
|
634
|
+
loadOptionalGeneratedModule(path.join(generatedDir, 'command-interceptors.generated.ts'), {
|
|
635
|
+
commandInterceptorEntries: [],
|
|
636
|
+
}),
|
|
637
|
+
loadOptionalGeneratedModule(path.join(generatedDir, 'workflows.generated.ts'), { allCodeWorkflows: [] }),
|
|
399
638
|
])
|
|
400
639
|
|
|
401
640
|
return {
|
|
@@ -423,6 +662,10 @@ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData
|
|
|
423
662
|
}
|
|
424
663
|
}
|
|
425
664
|
|
|
665
|
+
export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {
|
|
666
|
+
return withEsbuildLifecycle(() => loadBootstrapDataWithActiveEsbuild(appRoot))
|
|
667
|
+
}
|
|
668
|
+
|
|
426
669
|
/**
|
|
427
670
|
* Create and execute bootstrap in CLI context.
|
|
428
671
|
*
|
|
@@ -437,8 +680,15 @@ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData
|
|
|
437
680
|
*/
|
|
438
681
|
export async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {
|
|
439
682
|
const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')
|
|
440
|
-
const
|
|
441
|
-
|
|
683
|
+
const resolved = resolveAppRootOrThrow(appRoot)
|
|
684
|
+
// Both loads compile through esbuild, so they share one lifecycle scope: without it
|
|
685
|
+
// `loadBootstrapData` releases the esbuild helper process and `loadAppDiRegistrar`
|
|
686
|
+
// silently starts a second one that nothing ever stops.
|
|
687
|
+
const { data, appDiRegistrar } = await withEsbuildLifecycle(async () => ({
|
|
688
|
+
data: await loadBootstrapData(resolved.appDir),
|
|
689
|
+
appDiRegistrar: await loadAppDiRegistrar(resolved.appDir),
|
|
690
|
+
}))
|
|
691
|
+
const bootstrap = createBootstrap(data, appDiRegistrar ? { appDiRegistrar } : {})
|
|
442
692
|
bootstrap()
|
|
443
693
|
// In CLI context, wait for async registrations (UI widgets, search configs, etc.)
|
|
444
694
|
await waitForAsyncRegistration()
|
|
@@ -17,7 +17,7 @@ import { registerNotificationHandlers } from '../notifications/handler-registry'
|
|
|
17
17
|
import { clearRegisteredIntegrations, registerBundles, registerIntegrations } from '../../modules/integrations/types'
|
|
18
18
|
import { applyComponentOverridesToEntries } from '../../modules/overrides'
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const _bootstrappedKeys = new Set<string>()
|
|
21
21
|
|
|
22
22
|
// Store the async registration promise so callers can await it if needed
|
|
23
23
|
let _asyncRegistrationPromise: Promise<void> | null = null
|
|
@@ -34,13 +34,14 @@ let _asyncRegistrationPromise: Promise<void> | null = null
|
|
|
34
34
|
*/
|
|
35
35
|
export function createBootstrap(data: BootstrapData, options: BootstrapOptions = {}) {
|
|
36
36
|
return function bootstrap(): void {
|
|
37
|
+
const registrationKey = options.registrationKey ?? 'default'
|
|
37
38
|
if (options.appDiRegistrar) {
|
|
38
39
|
registerAppDiRegistrar(options.appDiRegistrar)
|
|
39
40
|
}
|
|
40
41
|
// In development, always re-run registrations to handle HMR
|
|
41
42
|
// (Module state may be reset when Turbopack reloads packages)
|
|
42
|
-
if (
|
|
43
|
-
|
|
43
|
+
if (_bootstrappedKeys.has(registrationKey) && process.env.NODE_ENV !== 'development') return
|
|
44
|
+
_bootstrappedKeys.add(registrationKey)
|
|
44
45
|
|
|
45
46
|
// === 1. Foundation: ORM entities and DI registrars ===
|
|
46
47
|
registerOrmEntities(data.entities)
|
|
@@ -138,21 +139,26 @@ export async function waitForAsyncRegistration(): Promise<void> {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
async function registerWidgetsAndOptionalPackages(data: BootstrapData, options: BootstrapOptions): Promise<void> {
|
|
141
|
-
// Register
|
|
142
|
+
// Register widget data required by server-side injection independently from
|
|
143
|
+
// browser-facing UI registries. API-only bootstraps avoid loading @open-mercato/ui.
|
|
142
144
|
try {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
])
|
|
148
|
-
|
|
149
|
-
dashboardRegistry.registerDashboardWidgets(data.dashboardWidgetEntries)
|
|
150
|
-
injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries)
|
|
151
|
-
coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)
|
|
145
|
+
const coreInjection = await import('@open-mercato/core/modules/widgets/lib/injection')
|
|
146
|
+
if (!options.skipCoreInjectionWidgets) {
|
|
147
|
+
coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)
|
|
148
|
+
}
|
|
152
149
|
coreInjection.registerCoreInjectionTables(data.injectionTables)
|
|
153
150
|
coreInjection.registerEnabledModuleIds(
|
|
154
151
|
data.modules.map((module) => module.id).filter((id): id is string => typeof id === 'string' && id.length > 0),
|
|
155
152
|
)
|
|
153
|
+
|
|
154
|
+
if (!options.skipUiRegistries) {
|
|
155
|
+
const [dashboardRegistry, injectionRegistry] = await Promise.all([
|
|
156
|
+
import('@open-mercato/ui/backend/dashboard/widgetRegistry'),
|
|
157
|
+
import('@open-mercato/ui/backend/injection/widgetRegistry'),
|
|
158
|
+
])
|
|
159
|
+
dashboardRegistry.registerDashboardWidgets(data.dashboardWidgetEntries)
|
|
160
|
+
injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries)
|
|
161
|
+
}
|
|
156
162
|
} catch {
|
|
157
163
|
// UI packages may not be available in all contexts
|
|
158
164
|
}
|
|
@@ -170,12 +176,12 @@ async function registerWidgetsAndOptionalPackages(data: BootstrapData, options:
|
|
|
170
176
|
* Check if bootstrap has been called.
|
|
171
177
|
*/
|
|
172
178
|
export function isBootstrapped(): boolean {
|
|
173
|
-
return
|
|
179
|
+
return _bootstrappedKeys.size > 0
|
|
174
180
|
}
|
|
175
181
|
|
|
176
182
|
/**
|
|
177
183
|
* Reset bootstrap state. Useful for testing.
|
|
178
184
|
*/
|
|
179
185
|
export function resetBootstrapState(): void {
|
|
180
|
-
|
|
186
|
+
_bootstrappedKeys.clear()
|
|
181
187
|
}
|
|
@@ -69,6 +69,12 @@ export interface BootstrapData {
|
|
|
69
69
|
|
|
70
70
|
export interface BootstrapOptions {
|
|
71
71
|
skipSearchConfigs?: boolean
|
|
72
|
+
/** Independent registration scope for partitioned runtime bootstraps. */
|
|
73
|
+
registrationKey?: string
|
|
74
|
+
/** Skip browser-facing dashboard/injection registries in API-only runtimes. */
|
|
75
|
+
skipUiRegistries?: boolean
|
|
76
|
+
/** Keep an existing core injection-widget registry intact in partitioned runtimes. */
|
|
77
|
+
skipCoreInjectionWidgets?: boolean
|
|
72
78
|
onRegistrationComplete?: () => void
|
|
73
79
|
appDiRegistrar?: AppDiRegistrar
|
|
74
80
|
}
|