@openmrs/esm-config 11.0.1-pre.5776 → 11.0.1-pre.5799
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/dist/module-config/module-config.d.ts.map +1 -1
- package/dist/module-config/module-config.js +15 -4
- package/dist/module-config/state.d.ts +6 -0
- package/dist/module-config/state.d.ts.map +1 -1
- package/dist/module-config/state.js +2 -1
- package/package.json +7 -7
- package/src/module-config/config-derivation-failure.test.ts +1 -1
- package/src/module-config/implementer-tools-config.test.ts +1 -1
- package/src/module-config/module-config.test.ts +99 -0
- package/src/module-config/module-config.ts +15 -3
- package/src/module-config/state.ts +7 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-config.d.ts","sourceRoot":"","sources":["../../src/module-config/module-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAuB,MAAM,UAAU,CAAC;AAkWxF;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,QAS1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,QAKhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"module-config.d.ts","sourceRoot":"","sources":["../../src/module-config/module-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAuB,MAAM,UAAU,CAAC;AAkWxF;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,QAS1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,MAAM,QAKhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,QAMpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,QAK7D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,QAetF;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,SAAa,QAK9D;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAMjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CA2BxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAKvG;AAioBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,QAUjD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,SAYhC"}
|
|
@@ -178,8 +178,8 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState, s
|
|
|
178
178
|
const configs = {};
|
|
179
179
|
let changed = false;
|
|
180
180
|
let entryCount = 0;
|
|
181
|
-
//
|
|
182
|
-
// it
|
|
181
|
+
// An extension can be mounted before its module loads, so the module schema may still be the
|
|
182
|
+
// implicit one; `computeExtensionConfig` holds back structural validation until it has started.
|
|
183
183
|
for (let extension of extensionState.mountedExtensions){
|
|
184
184
|
const previous = oldConfigs[extension.slotName]?.[extension.extensionId];
|
|
185
185
|
let entry;
|
|
@@ -286,6 +286,10 @@ function computeExtensionConfigs(configState, extensionState, tempConfigState, s
|
|
|
286
286
|
moduleLoaded: {
|
|
287
287
|
...state.moduleLoaded,
|
|
288
288
|
[moduleName]: true
|
|
289
|
+
},
|
|
290
|
+
moduleStarted: {
|
|
291
|
+
...state.moduleStarted,
|
|
292
|
+
[moduleName]: true
|
|
289
293
|
}
|
|
290
294
|
}));
|
|
291
295
|
}
|
|
@@ -455,6 +459,9 @@ const extensionConfigCache = new Map();
|
|
|
455
459
|
*/ function computeExtensionConfig(slotModuleName, extensionModuleName, slotName, extensionId, configState, tempConfigState, slotConfig) {
|
|
456
460
|
const extensionName = getExtensionNameFromId(extensionId);
|
|
457
461
|
const extensionConfigSchema = configState.schemas[extensionName];
|
|
462
|
+
// Not `moduleLoaded`: `defineConfigSchema` sets that while `startupApp` is still running, before it
|
|
463
|
+
// reaches `defineExtensionConfigSchema`, so the extension would be checked against the module schema.
|
|
464
|
+
const extensionModuleStarted = Boolean(configState.moduleStarted[extensionModuleName]);
|
|
458
465
|
// JSON string of these elements should be unique-per-extension
|
|
459
466
|
const cacheKey = JSON.stringify([
|
|
460
467
|
slotName,
|
|
@@ -463,7 +470,7 @@ const extensionConfigCache = new Map();
|
|
|
463
470
|
extensionModuleName
|
|
464
471
|
]);
|
|
465
472
|
const cached = extensionConfigCache.get(cacheKey);
|
|
466
|
-
if (cached && cached.extensionSchema === extensionConfigSchema && cached.extensionModuleSchema === configState.schemas[extensionModuleName] && cached.providedConfigs === configState.providedConfigs && cached.temporaryConfig === tempConfigState.config) {
|
|
473
|
+
if (cached && cached.extensionSchema === extensionConfigSchema && cached.extensionModuleSchema === configState.schemas[extensionModuleName] && cached.extensionModuleStarted === extensionModuleStarted && cached.providedConfigs === configState.providedConfigs && cached.temporaryConfig === tempConfigState.config) {
|
|
467
474
|
return cached.config;
|
|
468
475
|
}
|
|
469
476
|
const nameOfSchemaSource = extensionConfigSchema ? extensionName : extensionModuleName;
|
|
@@ -478,13 +485,17 @@ const extensionConfigCache = new Map();
|
|
|
478
485
|
configOverride
|
|
479
486
|
]);
|
|
480
487
|
const schema = extensionConfigSchema ?? configState.schemas[extensionModuleName];
|
|
481
|
-
|
|
488
|
+
// only validate structure once the schema has loaded
|
|
489
|
+
if (extensionConfigSchema || extensionModuleStarted) {
|
|
490
|
+
validateStructure(schema, combinedConfig, nameOfSchemaSource);
|
|
491
|
+
}
|
|
482
492
|
const config = setDefaults(schema, combinedConfig);
|
|
483
493
|
runAllValidatorsInConfigTree(schema, config, nameOfSchemaSource);
|
|
484
494
|
delete config.extensionSlots;
|
|
485
495
|
extensionConfigCache.set(cacheKey, {
|
|
486
496
|
extensionSchema: extensionConfigSchema,
|
|
487
497
|
extensionModuleSchema: configState.schemas[extensionModuleName],
|
|
498
|
+
extensionModuleStarted,
|
|
488
499
|
providedConfigs: configState.providedConfigs,
|
|
489
500
|
temporaryConfig: tempConfigState.config,
|
|
490
501
|
config
|
|
@@ -16,6 +16,12 @@ export interface ConfigInternalStore {
|
|
|
16
16
|
* whether the schema has actually been defined).
|
|
17
17
|
*/
|
|
18
18
|
moduleLoaded: Record<string, boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the app shell has finished starting each module (run its `startupApp`). Unlike
|
|
21
|
+
* `moduleLoaded`, `defineConfigSchema` does not set this, so it stays false while `startupApp`
|
|
22
|
+
* is still defining the module's extension schemas.
|
|
23
|
+
*/
|
|
24
|
+
moduleStarted: Record<string, boolean>;
|
|
19
25
|
}
|
|
20
26
|
/**
|
|
21
27
|
* @internal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/module-config/state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAExG;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,eAAe,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;IACvC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACtC;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/module-config/state.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-config",
|
|
3
|
-
"version": "11.0.1-pre.
|
|
3
|
+
"version": "11.0.1-pre.5799",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "A configuration library for the OpenMRS Single-Spa framework.",
|
|
6
6
|
"type": "module",
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
"lodash-es": "^4.17.21"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@openmrs/esm-globals": "^11.0.1-pre.
|
|
61
|
-
"@openmrs/esm-state": "^11.0.1-pre.
|
|
62
|
-
"@openmrs/esm-utils": "^11.0.1-pre.
|
|
60
|
+
"@openmrs/esm-globals": "^11.0.1-pre.5799",
|
|
61
|
+
"@openmrs/esm-state": "^11.0.1-pre.5799",
|
|
62
|
+
"@openmrs/esm-utils": "^11.0.1-pre.5799",
|
|
63
63
|
"single-spa": "6.x"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@openmrs/esm-globals": "11.0.1-pre.
|
|
67
|
-
"@openmrs/esm-state": "11.0.1-pre.
|
|
68
|
-
"@openmrs/esm-utils": "11.0.1-pre.
|
|
66
|
+
"@openmrs/esm-globals": "11.0.1-pre.5799",
|
|
67
|
+
"@openmrs/esm-state": "11.0.1-pre.5799",
|
|
68
|
+
"@openmrs/esm-utils": "11.0.1-pre.5799",
|
|
69
69
|
"@swc/cli": "0.8.1",
|
|
70
70
|
"@swc/core": "1.15.21",
|
|
71
71
|
"@types/lodash-es": "^4.17.12",
|
|
@@ -18,7 +18,7 @@ describe('a configuration that cannot be derived', () => {
|
|
|
18
18
|
beforeEach(() => {
|
|
19
19
|
consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
20
20
|
temporaryConfigStore.setState({ config: {} });
|
|
21
|
-
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {} });
|
|
21
|
+
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {}, moduleStarted: {} });
|
|
22
22
|
implementerToolsConfigStore.setState({ config: {}, derivationError: undefined });
|
|
23
23
|
resetConfigSystem();
|
|
24
24
|
defineConfigSchema('esm-probe', { thing: { _type: Type.Object, _default: {} } });
|
|
@@ -28,7 +28,7 @@ type ImplementerToolsTree = Record<string, ConfigNode>;
|
|
|
28
28
|
describe('implementer tools config', () => {
|
|
29
29
|
beforeEach(() => {
|
|
30
30
|
temporaryConfigStore.setState({ config: {} });
|
|
31
|
-
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {} });
|
|
31
|
+
configInternalStore.setState({ providedConfigs: [], schemas: {}, moduleLoaded: {}, moduleStarted: {} });
|
|
32
32
|
resetConfigSystem();
|
|
33
33
|
defineConfigSchema('esm-flintstone', {
|
|
34
34
|
label: { _type: Type.String, _default: 'default-label', _description: 'x' },
|
|
@@ -1590,6 +1590,105 @@ describe('extension config', () => {
|
|
|
1590
1590
|
});
|
|
1591
1591
|
});
|
|
1592
1592
|
|
|
1593
|
+
// `renderExtension` registers an extension before awaiting its bundle, so its config is derived
|
|
1594
|
+
// while the module is known to the config system only by the implicit schema.
|
|
1595
|
+
describe('extension config of a module that has not loaded yet', () => {
|
|
1596
|
+
beforeEach(() => {
|
|
1597
|
+
console.error = vi.fn();
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1600
|
+
afterEach(resetAll);
|
|
1601
|
+
|
|
1602
|
+
const mount = (extensionModuleName: string, extensionId: string) =>
|
|
1603
|
+
configExtensionStore.setState({
|
|
1604
|
+
mountedExtensions: [{ slotModuleName: 'slot-mod', extensionModuleName, slotName: 'early-slot', extensionId }],
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
it('does not call a key unknown before the module defines its schema', () => {
|
|
1608
|
+
Config.provide({ 'early-mod': { thud: 'provided' } });
|
|
1609
|
+
Config.registerModuleWithConfigSystem('early-mod');
|
|
1610
|
+
mount('early-mod', 'earlyExt');
|
|
1611
|
+
|
|
1612
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
1613
|
+
|
|
1614
|
+
Config.defineConfigSchema('early-mod', { thud: { _default: 'default' } });
|
|
1615
|
+
Config.registerModuleLoad('early-mod');
|
|
1616
|
+
|
|
1617
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
1618
|
+
expect(currentExtensionConfig('early-slot', 'earlyExt').config.thud).toBe('provided');
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
it('still calls a key unknown once the module has defined a schema without it', () => {
|
|
1622
|
+
Config.provide({ 'late-mod': { thud: 'provided', plugh: 'unknown' } });
|
|
1623
|
+
Config.registerModuleWithConfigSystem('late-mod');
|
|
1624
|
+
mount('late-mod', 'lateExt');
|
|
1625
|
+
|
|
1626
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
1627
|
+
|
|
1628
|
+
Config.defineConfigSchema('late-mod', { thud: { _default: 'default' } });
|
|
1629
|
+
Config.registerModuleLoad('late-mod');
|
|
1630
|
+
|
|
1631
|
+
expect(console.error).toHaveBeenCalledTimes(1);
|
|
1632
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/unknown config key 'late-mod.plugh' provided/i));
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
it('still calls a key unknown once a module without a schema has loaded', () => {
|
|
1636
|
+
// Configured through the slot so that only the extension config, not the module config, sees it.
|
|
1637
|
+
Config.provide({
|
|
1638
|
+
'slot-mod': { extensionSlots: { 'early-slot': { configure: { bareExt: { xyzzy: 'unknown' } } } } },
|
|
1639
|
+
});
|
|
1640
|
+
Config.registerModuleWithConfigSystem('bare-mod');
|
|
1641
|
+
mount('bare-mod', 'bareExt');
|
|
1642
|
+
|
|
1643
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
1644
|
+
|
|
1645
|
+
Config.registerModuleLoad('bare-mod');
|
|
1646
|
+
|
|
1647
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/unknown config key 'bare-mod.xyzzy' provided/i));
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
it('still reports a value of the wrong type once the module has defined its schema', () => {
|
|
1651
|
+
Config.provide({ 'typed-mod': { thud: 42 } });
|
|
1652
|
+
Config.registerModuleWithConfigSystem('typed-mod');
|
|
1653
|
+
mount('typed-mod', 'typedExt');
|
|
1654
|
+
|
|
1655
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
1656
|
+
|
|
1657
|
+
// `defineConfigSchema` marks the module loaded, so the module config reports both errors here. The
|
|
1658
|
+
// extension's own structure check waits for `registerModuleLoad`, and its repeats are deduplicated.
|
|
1659
|
+
Config.defineConfigSchema('typed-mod', {
|
|
1660
|
+
thud: { _type: Type.String, _default: 'default', _validators: [validator((v) => v !== 42, 'must not be 42')] },
|
|
1661
|
+
});
|
|
1662
|
+
Config.registerModuleLoad('typed-mod');
|
|
1663
|
+
|
|
1664
|
+
expect(console.error).toHaveBeenCalledTimes(2);
|
|
1665
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/42 for typed-mod.thud: must be a string/));
|
|
1666
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/42 for typed-mod.thud: must not be 42/));
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
it('does not call an extension schema key unknown while its module starts up', () => {
|
|
1670
|
+
Config.provide({
|
|
1671
|
+
'slot-mod': { extensionSlots: { 'early-slot': { configure: { flagsExt: { quux: 'x', corge: 'unknown' } } } } },
|
|
1672
|
+
});
|
|
1673
|
+
Config.registerModuleWithConfigSystem('flags-mod');
|
|
1674
|
+
mount('flags-mod', 'flagsExt');
|
|
1675
|
+
Config.defineConfigSchema('flags-mod', { thud: { _default: 'd' } });
|
|
1676
|
+
|
|
1677
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
1678
|
+
|
|
1679
|
+
// The extension's own schema is checked as soon as it is defined, without waiting for startup.
|
|
1680
|
+
Config.defineExtensionConfigSchema('flagsExt', { quux: { _default: 'd' } });
|
|
1681
|
+
|
|
1682
|
+
expect(console.error).toHaveBeenCalledTimes(1);
|
|
1683
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringMatching(/unknown config key 'flagsExt.corge' provided/i));
|
|
1684
|
+
expect(currentExtensionConfig('early-slot', 'flagsExt').config.quux).toBe('x');
|
|
1685
|
+
|
|
1686
|
+
Config.registerModuleLoad('flags-mod');
|
|
1687
|
+
|
|
1688
|
+
expect(console.error).toHaveBeenCalledTimes(1);
|
|
1689
|
+
});
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1593
1692
|
describe('translation overrides', () => {
|
|
1594
1693
|
it('allows obtaining translation overrides before schema is registered', async () => {
|
|
1595
1694
|
console.error = vi.fn();
|
|
@@ -275,8 +275,8 @@ function computeExtensionConfigs(
|
|
|
275
275
|
let changed = false;
|
|
276
276
|
let entryCount = 0;
|
|
277
277
|
|
|
278
|
-
//
|
|
279
|
-
// it
|
|
278
|
+
// An extension can be mounted before its module loads, so the module schema may still be the
|
|
279
|
+
// implicit one; `computeExtensionConfig` holds back structural validation until it has started.
|
|
280
280
|
for (let extension of extensionState.mountedExtensions) {
|
|
281
281
|
const previous = oldConfigs[extension.slotName]?.[extension.extensionId];
|
|
282
282
|
let entry: ConfigStore | { config: ConfigObject; loaded: true };
|
|
@@ -406,6 +406,7 @@ export function registerModuleLoad(moduleName: string) {
|
|
|
406
406
|
configInternalStore.setState((state) => ({
|
|
407
407
|
...state,
|
|
408
408
|
moduleLoaded: { ...state.moduleLoaded, [moduleName]: true },
|
|
409
|
+
moduleStarted: { ...state.moduleStarted, [moduleName]: true },
|
|
409
410
|
}));
|
|
410
411
|
}
|
|
411
412
|
|
|
@@ -602,6 +603,7 @@ export function processConfig(schema: ConfigSchema, providedConfig: ConfigObject
|
|
|
602
603
|
interface ExtensionConfigCacheEntry {
|
|
603
604
|
extensionSchema: ConfigSchema | undefined;
|
|
604
605
|
extensionModuleSchema: ConfigSchema | undefined;
|
|
606
|
+
extensionModuleStarted: boolean;
|
|
605
607
|
providedConfigs: ConfigInternalStore['providedConfigs'];
|
|
606
608
|
temporaryConfig: Config;
|
|
607
609
|
config: ConfigObject;
|
|
@@ -634,6 +636,9 @@ function computeExtensionConfig(
|
|
|
634
636
|
) {
|
|
635
637
|
const extensionName = getExtensionNameFromId(extensionId);
|
|
636
638
|
const extensionConfigSchema = configState.schemas[extensionName];
|
|
639
|
+
// Not `moduleLoaded`: `defineConfigSchema` sets that while `startupApp` is still running, before it
|
|
640
|
+
// reaches `defineExtensionConfigSchema`, so the extension would be checked against the module schema.
|
|
641
|
+
const extensionModuleStarted = Boolean(configState.moduleStarted[extensionModuleName]);
|
|
637
642
|
// JSON string of these elements should be unique-per-extension
|
|
638
643
|
const cacheKey = JSON.stringify([slotName, extensionId, slotModuleName, extensionModuleName]);
|
|
639
644
|
const cached = extensionConfigCache.get(cacheKey);
|
|
@@ -642,6 +647,7 @@ function computeExtensionConfig(
|
|
|
642
647
|
cached &&
|
|
643
648
|
cached.extensionSchema === extensionConfigSchema &&
|
|
644
649
|
cached.extensionModuleSchema === configState.schemas[extensionModuleName] &&
|
|
650
|
+
cached.extensionModuleStarted === extensionModuleStarted &&
|
|
645
651
|
cached.providedConfigs === configState.providedConfigs &&
|
|
646
652
|
cached.temporaryConfig === tempConfigState.config
|
|
647
653
|
) {
|
|
@@ -656,7 +662,12 @@ function computeExtensionConfig(
|
|
|
656
662
|
const extensionConfig = mergeConfigsFor(nameOfSchemaSource, providedConfigs);
|
|
657
663
|
const combinedConfig = mergeConfigs([extensionConfig, sharedOverride, configOverride]);
|
|
658
664
|
const schema = extensionConfigSchema ?? configState.schemas[extensionModuleName];
|
|
659
|
-
|
|
665
|
+
|
|
666
|
+
// only validate structure once the schema has loaded
|
|
667
|
+
if (extensionConfigSchema || extensionModuleStarted) {
|
|
668
|
+
validateStructure(schema, combinedConfig, nameOfSchemaSource);
|
|
669
|
+
}
|
|
670
|
+
|
|
660
671
|
const config = setDefaults(schema, combinedConfig);
|
|
661
672
|
runAllValidatorsInConfigTree(schema, config, nameOfSchemaSource);
|
|
662
673
|
delete config.extensionSlots;
|
|
@@ -664,6 +675,7 @@ function computeExtensionConfig(
|
|
|
664
675
|
extensionConfigCache.set(cacheKey, {
|
|
665
676
|
extensionSchema: extensionConfigSchema,
|
|
666
677
|
extensionModuleSchema: configState.schemas[extensionModuleName],
|
|
678
|
+
extensionModuleStarted,
|
|
667
679
|
providedConfigs: configState.providedConfigs,
|
|
668
680
|
temporaryConfig: tempConfigState.config,
|
|
669
681
|
config,
|
|
@@ -19,12 +19,19 @@ export interface ConfigInternalStore {
|
|
|
19
19
|
* whether the schema has actually been defined).
|
|
20
20
|
*/
|
|
21
21
|
moduleLoaded: Record<string, boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the app shell has finished starting each module (run its `startupApp`). Unlike
|
|
24
|
+
* `moduleLoaded`, `defineConfigSchema` does not set this, so it stays false while `startupApp`
|
|
25
|
+
* is still defining the module's extension schemas.
|
|
26
|
+
*/
|
|
27
|
+
moduleStarted: Record<string, boolean>;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
const configInternalStoreInitialValue = {
|
|
25
31
|
providedConfigs: [],
|
|
26
32
|
schemas: {},
|
|
27
33
|
moduleLoaded: {},
|
|
34
|
+
moduleStarted: {},
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
/**
|