11import type {
22 BasePlugin ,
3+ BasePluginConfig ,
34 CacheConfig ,
45 InputPluginMap ,
56 OptionalConfigPluginDef ,
@@ -9,9 +10,18 @@ import type {
910} from "shared" ;
1011import { CacheManager } from "../cache" ;
1112import { ServiceContext } from "../context" ;
13+ import { ConfigurationError } from "../errors" ;
14+ import { createLogger } from "../logging" ;
15+ import {
16+ getPluginManifest ,
17+ ResourceRegistry ,
18+ type ResourceRequirement ,
19+ } from "../registry" ;
1220import type { TelemetryConfig } from "../telemetry" ;
1321import { TelemetryManager } from "../telemetry" ;
1422
23+ const logger = createLogger ( "appkit" ) ;
24+
1525export class AppKit < TPlugins extends InputPluginMap > {
1626 #pluginInstances: Record < string , BasePlugin > = { } ;
1727 #setupPromises: Promise < void > [ ] = [ ] ;
@@ -152,6 +162,90 @@ export class AppKit<TPlugins extends InputPluginMap> {
152162 await ServiceContext . initialize ( ) ;
153163
154164 const rawPlugins = config . plugins as T ;
165+
166+ // Phase 1a: Collect resource requirements from all plugins
167+ const registry = ResourceRegistry . getInstance ( ) ;
168+ registry . clear ( ) ; // Clear any previous state
169+
170+ for ( const pluginData of rawPlugins ) {
171+ if ( ! pluginData ?. plugin ) continue ;
172+
173+ const pluginName = pluginData . name ;
174+
175+ // Load manifest and register static resources
176+ try {
177+ const manifest = getPluginManifest ( pluginData . plugin ) ;
178+
179+ // Register required resources
180+ for ( const resource of manifest . resources . required ) {
181+ registry . register ( pluginName , { ...resource , required : true } ) ;
182+ }
183+
184+ // Register optional resources
185+ for ( const resource of manifest . resources . optional || [ ] ) {
186+ registry . register ( pluginName , { ...resource , required : false } ) ;
187+ }
188+
189+ // Check for runtime resource requirements
190+ if ( typeof pluginData . plugin . getResourceRequirements === "function" ) {
191+ const runtimeResources = pluginData . plugin . getResourceRequirements (
192+ pluginData . config as BasePluginConfig ,
193+ ) ;
194+ for ( const resource of runtimeResources ) {
195+ // Cast from shared's ResourceRequirement to registry's ResourceRequirement
196+ // The shared type has looser typing (string) vs registry (ResourceType enum)
197+ registry . register ( pluginName , resource as ResourceRequirement ) ;
198+ }
199+ }
200+
201+ logger . debug (
202+ "Collected resources from plugin %s: %d total" ,
203+ pluginName ,
204+ registry . getByPlugin ( pluginName ) . length ,
205+ ) ;
206+ } catch ( error ) {
207+ // Plugin doesn't have a manifest - this is allowed for legacy plugins
208+ // or plugins that don't declare resources
209+ logger . debug (
210+ "Plugin %s has no manifest or invalid manifest: %s" ,
211+ pluginName ,
212+ error instanceof Error ? error . message : String ( error ) ,
213+ ) ;
214+ }
215+ }
216+
217+ // Phase 1b: Validate resources
218+ const validation = registry . validate ( ) ;
219+ const isDevelopment = process . env . NODE_ENV === "development" ;
220+
221+ if ( ! validation . valid ) {
222+ const errorMessage = ResourceRegistry . formatMissingResources (
223+ validation . missing ,
224+ ) ;
225+
226+ if ( isDevelopment ) {
227+ // In development mode, warn but continue
228+ logger . warn (
229+ "Missing resources detected (continuing in dev mode):\n%s" ,
230+ errorMessage ,
231+ ) ;
232+ } else {
233+ // In production, throw error
234+ throw new ConfigurationError ( errorMessage , {
235+ context : {
236+ missingResources : validation . missing . map ( ( r ) => ( {
237+ type : r . type ,
238+ alias : r . alias ,
239+ plugin : r . plugin ,
240+ env : r . env ,
241+ } ) ) ,
242+ } ,
243+ } ) ;
244+ }
245+ } else if ( registry . size ( ) > 0 ) {
246+ logger . debug ( "All %d resources validated successfully" , registry . size ( ) ) ;
247+ }
248+
155249 const preparedPlugins = AppKit . preparePlugins ( rawPlugins ) ;
156250 const mergedConfig = {
157251 plugins : preparedPlugins ,
0 commit comments