Skip to content

Commit 27ef3ed

Browse files
committed
Migrate channel spec to ChannelModule subclass (first proof of concept)
Phase 3b: First spec migrated to the new ExtensionInstance subclass pattern. - Create ChannelModule extending ExtensionInstance directly with: - copy_files build config - include_assets client steps - contract-based deploy config - Create channelDescriptor as a ModuleDescriptor - Register descriptor in loadModuleRegistry() - Remove channel from legacy spec list - Wire moduleRegistry into loadAppFromContext pipeline Channel extensions are now created via descriptor.createModule() instead of new SpecificationBackedExtension() with a factory-generated spec. 513 model tests pass.
1 parent 8c23aab commit 27ef3ed

2 files changed

Lines changed: 94 additions & 3 deletions

File tree

packages/app/src/cli/models/extensions/load-specifications.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import themeSpec from './specifications/theme.js'
2727
import uiExtensionSpec from './specifications/ui_extension.js'
2828
import webPixelSpec from './specifications/web_pixel_extension.js'
2929
import editorExtensionCollectionSpecification from './specifications/editor_extension_collection.js'
30-
import channelSpecificationSpec from './specifications/channel.js'
3130
import adminSpecificationSpec from './specifications/admin.js'
31+
import {channelDescriptor} from './specifications/channel_module.js'
3232

3333
const SORTED_CONFIGURATION_SPEC_IDENTIFIERS = [
3434
BrandingSpecIdentifier,
@@ -59,7 +59,7 @@ export async function loadLocalExtensionsSpecifications(): Promise<ExtensionSpec
5959
*/
6060
export function loadModuleRegistry(): ModuleRegistry {
6161
const registry = new ModuleRegistry()
62-
// Descriptors will be registered here as specs are migrated.
62+
registry.register(channelDescriptor)
6363
return registry
6464
}
6565

@@ -91,7 +91,6 @@ function loadSpecifications() {
9191
uiExtensionSpec,
9292
webPixelSpec,
9393
editorExtensionCollectionSpecification,
94-
channelSpecificationSpec,
9594
]
9695

9796
return [...configModuleSpecs, ...moduleSpecs] as ExtensionSpecification[]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {ApplicationModule, ExtensionDeployConfigOptions} from '../application-module.js'
2+
import type {ExtensionFeature, BuildConfig} from '../application-module.js'
3+
import {ClientSteps} from '../../../services/build/client-steps.js'
4+
import {ModuleDescriptor} from '../module-descriptor.js'
5+
import {RemoteSpecification} from '../../../api/graphql/extension_specifications.js'
6+
import {BaseConfigType, BaseSchema} from '../schemas.js'
7+
import {configWithoutFirstClassFields} from '../specification.js'
8+
import {loadLocalesConfig} from '../../../utilities/extensions/locales-configuration.js'
9+
import {joinPath} from '@shopify/cli-kit/node/path'
10+
import {zod} from '@shopify/cli-kit/node/schema'
11+
import {blocks} from '../../../constants.js'
12+
13+
const SUBDIRECTORY_NAME = 'specifications'
14+
const FILE_EXTENSIONS = ['json', 'toml', 'yaml', 'yml', 'svg']
15+
16+
export const ChannelModuleIdentifier = 'channel_config'
17+
18+
class ChannelModule extends ApplicationModule {
19+
constructor(options: {
20+
configuration: BaseConfigType
21+
configurationPath: string
22+
entryPath?: string
23+
directory: string
24+
remoteSpec: RemoteSpecification
25+
}) {
26+
super(options)
27+
this.initialize()
28+
}
29+
30+
appModuleFeatures(): ExtensionFeature[] {
31+
return []
32+
}
33+
34+
override get buildConfig(): BuildConfig {
35+
return {
36+
mode: 'copy_files',
37+
filePatterns: FILE_EXTENSIONS.map((ext) => joinPath(SUBDIRECTORY_NAME, '**', `*.${ext}`)),
38+
}
39+
}
40+
41+
override get clientSteps(): ClientSteps {
42+
return [
43+
{
44+
lifecycle: 'deploy',
45+
steps: [
46+
{
47+
id: 'copy-files',
48+
name: 'Copy Files',
49+
type: 'include_assets',
50+
config: {
51+
inclusions: [
52+
{
53+
type: 'pattern',
54+
baseDir: SUBDIRECTORY_NAME,
55+
destination: SUBDIRECTORY_NAME,
56+
include: FILE_EXTENSIONS.map((ext) => `**/*.${ext}`),
57+
},
58+
],
59+
},
60+
},
61+
],
62+
},
63+
]
64+
}
65+
66+
override async deployConfig(_options: ExtensionDeployConfigOptions): Promise<Record<string, unknown> | undefined> {
67+
let parsedConfig = configWithoutFirstClassFields(this.configuration)
68+
if (this.appModuleFeatures().includes('localization')) {
69+
const localization = await loadLocalesConfig(this.directory, this.identifier)
70+
parsedConfig = {...parsedConfig, localization}
71+
}
72+
return parsedConfig
73+
}
74+
}
75+
76+
export const channelDescriptor: ModuleDescriptor = {
77+
identifier: ChannelModuleIdentifier,
78+
additionalIdentifiers: [],
79+
externalIdentifier: `${ChannelModuleIdentifier}_external`,
80+
externalName: 'Channel config',
81+
partnersWebIdentifier: ChannelModuleIdentifier,
82+
surface: 'test-surface',
83+
registrationLimit: blocks.extensions.defaultRegistrationLimit,
84+
experience: 'extension',
85+
uidStrategy: 'single',
86+
schema: zod.any({}) as unknown as typeof BaseSchema,
87+
contributeToAppConfigurationSchema: (schema) => schema,
88+
parseConfigurationObject: (configurationObject: object) => {
89+
return {state: 'ok' as const, data: configurationObject as BaseConfigType, errors: undefined}
90+
},
91+
createModule: (options) => new ChannelModule(options),
92+
}

0 commit comments

Comments
 (0)