Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions packages/app/src/cli/models/app/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import {configurationFileNames, dotEnvFileNames} from '../../constants.js'
import metadata from '../../metadata.js'
import {ExtensionInstance, SpecificationBackedExtension} from '../extensions/extension-instance.js'
import {ModuleRegistry} from '../extensions/module-registry.js'
import {loadModuleRegistry} from '../extensions/load-specifications.js'
import {ExtensionsArraySchema, UnifiedSchema} from '../extensions/schemas.js'
import {ExtensionSpecification, isAppConfigSpecification} from '../extensions/specification.js'
import {CreateAppOptions, Flag} from '../../utilities/developer-platform-client.js'
Expand Down Expand Up @@ -327,6 +329,23 @@
usesCliManagedUrls: configuration.build?.automatically_update_urls_on_dev,
}

// Build the module registry and merge remote spec values into it so
// descriptors have up-to-date identity data from the platform API.
const moduleRegistry = loadModuleRegistry()
const remoteSpecsForMerge = specifications.map((spec) => ({
name: spec.externalName,
externalName: spec.externalName,
identifier: spec.identifier,
gated: false,
externalIdentifier: spec.externalIdentifier,
experience: spec.experience as 'extension' | 'configuration' | 'deprecated',
managementExperience: 'cli' as const,
registrationLimit: spec.registrationLimit,
uidStrategy: spec.uidStrategy,
surface: spec.surface,
}))
moduleRegistry.mergeRemoteSpecs(remoteSpecsForMerge)

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > localAppContext > loads app with extensions

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.localAppContext src/cli/services/app-context.ts:186:15 ❯ src/cli/services/app-context.test.ts:447:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:357:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > localAppContext > uses userProvidedConfigName when provided

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.localAppContext src/cli/services/app-context.ts:186:15 ❯ src/cli/services/app-context.test.ts:340:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:323:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > localAppContext > loads app without network calls or linking

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.localAppContext src/cli/services/app-context.ts:186:15 ❯ src/cli/services/app-context.test.ts:301:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:284:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > does not throw when unsafeTolerateErrors is false and app has no errors

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:265:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:257:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > unsafeTolerateErrors skips throwIfErrors and addUidToTomlsIfNecessary

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:216:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:208:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > logs metadata

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:188:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:180:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > resets app when there is a valid toml but reset option is true

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:167:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:149:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > uses provided clientId when available and updates the app configuration

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:133:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:122:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > updates cached app info when remoteApp matches

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:101:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:86:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 20.14.0 in windows-latest (shard 2/2)

src/cli/services/app-context.test.ts > linkedAppContext > returns linked app context when app is already linked

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:58:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:50:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > localAppContext > loads app with extensions

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.localAppContext src/cli/services/app-context.ts:186:15 ❯ src/cli/services/app-context.test.ts:447:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:357:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > localAppContext > uses userProvidedConfigName when provided

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.localAppContext src/cli/services/app-context.ts:186:15 ❯ src/cli/services/app-context.test.ts:340:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:323:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > localAppContext > loads app without network calls or linking

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.localAppContext src/cli/services/app-context.ts:186:15 ❯ src/cli/services/app-context.test.ts:301:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:284:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > does not throw when unsafeTolerateErrors is false and app has no errors

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:265:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:257:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > unsafeTolerateErrors skips throwIfErrors and addUidToTomlsIfNecessary

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:216:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:208:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > logs metadata

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:188:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:180:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > resets app when there is a valid toml but reset option is true

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:167:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:149:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > uses provided clientId when available and updates the app configuration

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:133:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:122:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > updates cached app info when remoteApp matches

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:101:7 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:86:5

Check failure on line 347 in packages/app/src/cli/models/app/loader.ts

View workflow job for this annotation

GitHub Actions / Unit tests with Node 22.2.0 in ubuntu-latest

src/cli/services/app-context.test.ts > linkedAppContext > returns linked app context when app is already linked

TypeError: Cannot read properties of undefined (reading 'mergeRemoteSpecs') ❯ loadAppFromContext src/cli/models/app/loader.ts:347:18 ❯ Module.linkedAppContext src/cli/services/app-context.ts:119:20 ❯ src/cli/services/app-context.test.ts:58:22 ❯ Module.inTemporaryDirectory ../cli-kit/src/public/node/fs.ts:81:12 ❯ src/cli/services/app-context.test.ts:50:5

const loadedConfiguration: ConfigurationLoaderResult<CurrentAppConfiguration, TModuleSpec> = {
directory: project.directory,
configPath: configurationPath,
Expand All @@ -335,6 +354,7 @@
configSchema,
specifications,
remoteFlags,
moduleRegistry,
}

const loader = new AppLoader<CurrentAppConfiguration, TModuleSpec>({
Expand Down Expand Up @@ -466,6 +486,7 @@
private readonly loadedConfiguration: ConfigurationLoaderResult<TConfig, TModuleSpec>
private readonly reloadState: ReloadState | undefined
private readonly project: Project
private readonly moduleRegistry: ModuleRegistry

constructor({
ignoreUnknownExtensions,
Expand All @@ -479,6 +500,7 @@
this.loadedConfiguration = loadedConfiguration
this.reloadState = reloadState
this.project = project
this.moduleRegistry = loadedConfiguration.moduleRegistry ?? new ModuleRegistry()
}

private get activeConfigFile(): TomlFile | undefined {
Expand Down Expand Up @@ -594,6 +616,14 @@
configurationPath: string,
directory: string,
): Promise<ExtensionInstance | undefined> {
// Check the module registry first (new subclass-based path).
// If a descriptor is found, use its factory to create the right subclass.
const descriptor = this.moduleRegistry.findForType(type)
if (descriptor) {
return this.createModuleFromDescriptor(descriptor, configurationObject, configurationPath, directory)
}

// Fall back to legacy ExtensionSpecification path.
const specification = this.findSpecificationForType(type)
let entryPath
let usedKnownSpecification = false
Expand Down Expand Up @@ -649,6 +679,62 @@
return extensionInstance
}

private async createModuleFromDescriptor(
descriptor: ReturnType<ModuleRegistry['findForType']> & {},
configurationObject: object,
configurationPath: string,
directory: string,
): Promise<ExtensionInstance | undefined> {
const parseResult = descriptor.parseConfigurationObject(configurationObject)
if (parseResult.state === 'error') {
if (parseResult.errors) {
for (const error of parseResult.errors) {
this.errors.addError({
file: configurationPath,
message: error.message ?? `Validation error at ${error.path.join('.')}`,
})
}
}
return undefined
}

const configuration = parseResult.data
const entryPath = await this.findEntryPath(directory, descriptor)

const moduleInstance = descriptor.createModule({
configuration,
configurationPath,
entryPath,
directory,
remoteSpec: {
name: descriptor.externalName,
externalName: descriptor.externalName,
identifier: descriptor.identifier,
gated: false,
externalIdentifier: descriptor.externalIdentifier,
experience: descriptor.experience,
managementExperience: 'cli',
registrationLimit: descriptor.registrationLimit,
uidStrategy: descriptor.uidStrategy,
surface: descriptor.surface,
},
})

if (this.reloadState && configuration.handle) {
const previousDevUUID = this.reloadState.extensionDevUUIDs.get(configuration.handle)
if (previousDevUUID) {
moduleInstance.devUUID = previousDevUUID
}
}

const validateResult = await moduleInstance.validate()
if (validateResult.isErr()) {
this.errors.addError({file: configurationPath, message: stringifyMessage(validateResult.error).trim()})
}

return moduleInstance
}

private async loadExtensions(appDirectory: string, appConfiguration: TConfig): Promise<ExtensionInstance[]> {
if (this.specifications.length === 0) return []

Expand Down Expand Up @@ -827,9 +913,10 @@
return configContent ? extensionInstance : undefined
}

private async findEntryPath(directory: string, specification: ExtensionSpecification) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async findEntryPath(directory: string, specification: {identifier: string; appModuleFeatures?: (...args: any[]) => string[]}) {
let entryPath
if (specification.appModuleFeatures().includes('single_js_entry_path')) {
if (specification.appModuleFeatures?.().includes('single_js_entry_path')) {
entryPath = (
await Promise.all(
['index']
Expand Down Expand Up @@ -897,6 +984,7 @@
TModuleSpec extends ExtensionSpecification,
> = AppConfigurationInterface<TConfig, TModuleSpec> & {
configurationLoadResultMetadata: ConfigurationLoadResultMetadata
moduleRegistry?: ModuleRegistry
}

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/app/src/cli/models/extensions/load-specifications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {ExtensionSpecification} from './specification.js'
import {ModuleRegistry} from './module-registry.js'
import appHomeSpec, {AppHomeSpecIdentifier} from './specifications/app_config_app_home.js'
import appProxySpec, {AppProxySpecIdentifier} from './specifications/app_config_app_proxy.js'
import appPOSSpec, {PosSpecIdentifier} from './specifications/app_config_point_of_sale.js'
Expand Down Expand Up @@ -51,6 +52,17 @@ export async function loadLocalExtensionsSpecifications(): Promise<ExtensionSpec
return loadSpecifications().sort(sortConfigModules)
}

/**
* Load the module registry with all migrated module descriptors.
* Descriptors registered here use the new ApplicationModule subclass path
* instead of the legacy ExtensionSpecification factory pattern.
*/
export function loadModuleRegistry(): ModuleRegistry {
const registry = new ModuleRegistry()
// Descriptors will be registered here as specs are migrated.
return registry
}

function loadSpecifications() {
const configModuleSpecs = [
appAccessSpec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export async function fetchSpecifications({
return [...updatedSpecs]
}


async function mergeLocalAndRemoteSpecs(
local: ExtensionSpecification[],
remote: RemoteSpecification[],
Expand Down
Loading