Skip to content

Commit a9042c2

Browse files
committed
Wire loader to support ModuleRegistry alongside ExtensionSpecification
Phase 3a: Add dual-path instance creation to the AppLoader. - Add ModuleRegistry field to AppLoader - Update createExtensionInstance to check registry first via descriptor.createModule(), falling back to legacy spec path - Add createModuleFromDescriptor method for the new path - Widen findEntryPath signature to accept both specs and descriptors - Add moduleRegistry to ConfigurationLoaderResult type The registry is currently empty so all instances still use the legacy SpecificationBackedExtension path. Next PR will migrate actual specs.
1 parent afb85b0 commit a9042c2

File tree

1 file changed

+71
-2
lines changed

1 file changed

+71
-2
lines changed

packages/app/src/cli/models/app/loader.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {configurationFileNames, dotEnvFileNames} from '../../constants.js'
2222
import metadata from '../../metadata.js'
2323
import {ExtensionInstance, SpecificationBackedExtension} from '../extensions/extension-instance.js'
24+
import {ModuleRegistry} from '../extensions/module-registry.js'
2425
import {ExtensionsArraySchema, UnifiedSchema} from '../extensions/schemas.js'
2526
import {ExtensionSpecification, isAppConfigSpecification} from '../extensions/specification.js'
2627
import {CreateAppOptions, Flag} from '../../utilities/developer-platform-client.js'
@@ -459,6 +460,7 @@ class AppLoader<TConfig extends CurrentAppConfiguration, TModuleSpec extends Ext
459460
private readonly loadedConfiguration: ConfigurationLoaderResult<TConfig, TModuleSpec>
460461
private readonly reloadState: ReloadState | undefined
461462
private readonly project: Project
463+
private readonly moduleRegistry: ModuleRegistry
462464

463465
constructor({
464466
ignoreUnknownExtensions,
@@ -472,6 +474,7 @@ class AppLoader<TConfig extends CurrentAppConfiguration, TModuleSpec extends Ext
472474
this.loadedConfiguration = loadedConfiguration
473475
this.reloadState = reloadState
474476
this.project = project
477+
this.moduleRegistry = loadedConfiguration.moduleRegistry ?? new ModuleRegistry()
475478
}
476479

477480
private get activeConfigFile(): TomlFile | undefined {
@@ -587,6 +590,14 @@ class AppLoader<TConfig extends CurrentAppConfiguration, TModuleSpec extends Ext
587590
configurationPath: string,
588591
directory: string,
589592
): Promise<ExtensionInstance | undefined> {
593+
// Check the module registry first (new subclass-based path).
594+
// If a descriptor is found, use its factory to create the right subclass.
595+
const descriptor = this.moduleRegistry.findForType(type)
596+
if (descriptor) {
597+
return this.createModuleFromDescriptor(descriptor, configurationObject, configurationPath, directory)
598+
}
599+
600+
// Fall back to legacy ExtensionSpecification path.
590601
const specification = this.findSpecificationForType(type)
591602
let entryPath
592603
let usedKnownSpecification = false
@@ -642,6 +653,62 @@ class AppLoader<TConfig extends CurrentAppConfiguration, TModuleSpec extends Ext
642653
return extensionInstance
643654
}
644655

656+
private async createModuleFromDescriptor(
657+
descriptor: ReturnType<ModuleRegistry['findForType']> & {},
658+
configurationObject: object,
659+
configurationPath: string,
660+
directory: string,
661+
): Promise<ExtensionInstance | undefined> {
662+
const parseResult = descriptor.parseConfigurationObject(configurationObject)
663+
if (parseResult.state === 'error') {
664+
if (parseResult.errors) {
665+
for (const error of parseResult.errors) {
666+
this.errors.addError({
667+
file: configurationPath,
668+
message: error.message ?? `Validation error at ${error.path.join('.')}`,
669+
})
670+
}
671+
}
672+
return undefined
673+
}
674+
675+
const configuration = parseResult.data
676+
const entryPath = await this.findEntryPath(directory, descriptor)
677+
678+
const moduleInstance = descriptor.createModule({
679+
configuration,
680+
configurationPath,
681+
entryPath,
682+
directory,
683+
remoteSpec: {
684+
name: descriptor.externalName,
685+
externalName: descriptor.externalName,
686+
identifier: descriptor.identifier,
687+
gated: false,
688+
externalIdentifier: descriptor.externalIdentifier,
689+
experience: descriptor.experience,
690+
managementExperience: 'cli',
691+
registrationLimit: descriptor.registrationLimit,
692+
uidStrategy: descriptor.uidStrategy,
693+
surface: descriptor.surface,
694+
},
695+
})
696+
697+
if (this.reloadState && configuration.handle) {
698+
const previousDevUUID = this.reloadState.extensionDevUUIDs.get(configuration.handle)
699+
if (previousDevUUID) {
700+
moduleInstance.devUUID = previousDevUUID
701+
}
702+
}
703+
704+
const validateResult = await moduleInstance.validate()
705+
if (validateResult.isErr()) {
706+
this.errors.addError({file: configurationPath, message: stringifyMessage(validateResult.error).trim()})
707+
}
708+
709+
return moduleInstance
710+
}
711+
645712
private async loadExtensions(appDirectory: string, appConfiguration: TConfig): Promise<ExtensionInstance[]> {
646713
if (this.specifications.length === 0) return []
647714

@@ -820,9 +887,10 @@ class AppLoader<TConfig extends CurrentAppConfiguration, TModuleSpec extends Ext
820887
return configContent ? extensionInstance : undefined
821888
}
822889

823-
private async findEntryPath(directory: string, specification: ExtensionSpecification) {
890+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
891+
private async findEntryPath(directory: string, specification: {identifier: string; appModuleFeatures?: (...args: any[]) => string[]}) {
824892
let entryPath
825-
if (specification.appModuleFeatures().includes('single_js_entry_path')) {
893+
if (specification.appModuleFeatures?.().includes('single_js_entry_path')) {
826894
entryPath = (
827895
await Promise.all(
828896
['index']
@@ -890,6 +958,7 @@ type ConfigurationLoaderResult<
890958
TModuleSpec extends ExtensionSpecification,
891959
> = AppConfigurationInterface<TConfig, TModuleSpec> & {
892960
configurationLoadResultMetadata: ConfigurationLoadResultMetadata
961+
moduleRegistry?: ModuleRegistry
893962
}
894963

895964
/**

0 commit comments

Comments
 (0)