@@ -10,9 +10,22 @@ import type { ChannelId } from "../../channel-id.types.js";
1010import type { ChannelPlugin } from "../../types.js" ;
1111
1212type ChannelPluginApiModule = Record < string , unknown > ;
13+ type ChannelDirectoryContractModule = Record < string , unknown > ;
1314
1415const channelPluginCache = new Map < ChannelId , ChannelPlugin | null > ( ) ;
1516const channelPluginPromiseCache = new Map < ChannelId , Promise < ChannelPlugin | null > > ( ) ;
17+ const channelDirectoryPluginCache = new Map < ChannelId , Pick < ChannelPlugin , "id" | "directory" > | null > ( ) ;
18+ const channelDirectoryPluginPromiseCache = new Map <
19+ ChannelId ,
20+ Promise < Pick < ChannelPlugin , "id" | "directory" > | null >
21+ > ( ) ;
22+
23+ class MissingBundledDirectoryContractArtifactError extends Error {
24+ constructor ( id : ChannelId ) {
25+ super ( `Missing bundled directory contract artifact for ${ id } ` ) ;
26+ this . name = "MissingBundledDirectoryContractArtifactError" ;
27+ }
28+ }
1629
1730function isChannelPlugin ( value : unknown ) : value is ChannelPlugin {
1831 return (
@@ -24,6 +37,38 @@ function isChannelPlugin(value: unknown): value is ChannelPlugin {
2437 ) ;
2538}
2639
40+ function isChannelDirectoryContractPlugin (
41+ value : unknown ,
42+ ) : value is Pick < ChannelPlugin , "id" | "directory" > {
43+ return (
44+ Boolean ( value ) &&
45+ typeof value === "object" &&
46+ typeof ( value as Partial < ChannelPlugin > ) . id === "string" &&
47+ Boolean ( ( value as Partial < ChannelPlugin > ) . directory )
48+ ) ;
49+ }
50+
51+ function findChannelPlugin ( module : ChannelPluginApiModule ) : ChannelPlugin | null {
52+ return Object . values ( module ) . find ( isChannelPlugin ) ?? null ;
53+ }
54+
55+ function findChannelDirectoryContractPlugin (
56+ module : ChannelDirectoryContractModule ,
57+ ) : Pick < ChannelPlugin , "id" | "directory" > | null {
58+ return Object . values ( module ) . find ( isChannelDirectoryContractPlugin ) ?? null ;
59+ }
60+
61+ function hasBasePluginMetadata ( plugin : ChannelPlugin | null , id : ChannelId ) : boolean {
62+ return (
63+ plugin ?. id === id &&
64+ plugin . meta ?. id === id &&
65+ typeof plugin . meta . label === "string" &&
66+ typeof plugin . meta . selectionLabel === "string" &&
67+ typeof plugin . meta . docsPath === "string" &&
68+ typeof plugin . meta . blurb === "string"
69+ ) ;
70+ }
71+
2772function isBuiltArtifactMissingDependency ( error : unknown ) : boolean {
2873 const record = error as
2974 | {
@@ -101,6 +146,68 @@ async function importBundledChannelPluginSourceSurface(id: ChannelId) {
101146 return ( await import ( pathToFileURL ( sourcePath ) . href ) ) as ChannelPluginApiModule ;
102147}
103148
149+ function resolveSourceArtifactPath ( artifactPath : string ) : string {
150+ if ( artifactPath . endsWith ( ".js" ) && fs . existsSync ( `${ artifactPath . slice ( 0 , - 3 ) } .ts` ) ) {
151+ return `${ artifactPath . slice ( 0 , - 3 ) } .ts` ;
152+ }
153+ return artifactPath ;
154+ }
155+
156+ async function importBundledChannelDirectoryContractSourceSurface (
157+ id : ChannelId ,
158+ ) : Promise < ChannelDirectoryContractModule > {
159+ const artifactPath = resolveBundledPluginPublicModulePath ( {
160+ pluginId : id ,
161+ artifactBasename : "directory-contract-api.js" ,
162+ } ) ;
163+ const sourcePath = resolveSourceArtifactPath ( artifactPath ) ;
164+ if ( ! fs . existsSync ( sourcePath ) ) {
165+ throw new MissingBundledDirectoryContractArtifactError ( id ) ;
166+ }
167+ return ( await import ( pathToFileURL ( sourcePath ) . href ) ) as ChannelDirectoryContractModule ;
168+ }
169+
170+ function isMissingBundledDirectoryContractArtifact ( error : unknown , id : ChannelId ) : boolean {
171+ return (
172+ error instanceof Error &&
173+ error . message === `Unable to resolve bundled plugin public surface ${ id } /directory-contract-api.js`
174+ ) ;
175+ }
176+
177+ async function loadBundledChannelDirectoryContractSurface (
178+ id : ChannelId ,
179+ ) : Promise < ChannelDirectoryContractModule > {
180+ return await loadBundledPluginPublicSurface < ChannelDirectoryContractModule > ( {
181+ pluginId : id ,
182+ artifactBasename : "directory-contract-api.js" ,
183+ } ) . catch ( ( error : unknown ) => {
184+ if ( isMissingBundledDirectoryContractArtifact ( error , id ) ) {
185+ throw new MissingBundledDirectoryContractArtifactError ( id ) ;
186+ }
187+ if ( ! isBuiltArtifactMissingDependency ( error ) || ! canFallbackToPackageSource ( ) ) {
188+ throw error ;
189+ }
190+ return importBundledChannelDirectoryContractSourceSurface ( id ) ;
191+ } ) ;
192+ }
193+
194+ async function resolveBundledChannelPluginFromSurface (
195+ id : ChannelId ,
196+ loaded : ChannelPluginApiModule ,
197+ ) : Promise < ChannelPlugin | null > {
198+ const plugin = findChannelPlugin ( loaded ) ;
199+ if ( ! plugin ) {
200+ return plugin ;
201+ }
202+ if ( hasBasePluginMetadata ( plugin , id ) ) {
203+ return plugin ;
204+ }
205+
206+ const sourceLoaded = await importBundledChannelPluginSourceSurface ( id ) ;
207+ const sourcePlugin = findChannelPlugin ( sourceLoaded ) ;
208+ return sourcePlugin ?? plugin ;
209+ }
210+
104211export function listBundledChannelPluginIds ( ) : readonly ChannelId [ ] {
105212 return listCatalogBundledChannelPluginIds ( ) as ChannelId [ ] ;
106213}
@@ -127,8 +234,8 @@ export async function getBundledChannelPluginAsync(
127234 }
128235 return importBundledChannelPluginSourceSurface ( id ) ;
129236 } )
130- . then ( ( loaded ) => {
131- const plugin = Object . values ( loaded ) . find ( isChannelPlugin ) ?? null ;
237+ . then ( async ( loaded ) => {
238+ const plugin = await resolveBundledChannelPluginFromSurface ( id , loaded ) ;
132239 channelPluginCache . set ( id , plugin ) ;
133240 return plugin ;
134241 } )
@@ -138,3 +245,40 @@ export async function getBundledChannelPluginAsync(
138245 channelPluginPromiseCache . set ( id , loading ) ;
139246 return ( await loading ) ?? undefined ;
140247}
248+
249+ export async function getBundledChannelDirectoryPluginAsync (
250+ id : ChannelId ,
251+ ) : Promise < Pick < ChannelPlugin , "id" | "directory" > | undefined > {
252+ if ( channelDirectoryPluginCache . has ( id ) ) {
253+ return channelDirectoryPluginCache . get ( id ) ?? undefined ;
254+ }
255+
256+ const cachedPromise = channelDirectoryPluginPromiseCache . get ( id ) ;
257+ if ( cachedPromise ) {
258+ return ( await cachedPromise ) ?? undefined ;
259+ }
260+
261+ const loading = loadBundledChannelDirectoryContractSurface ( id )
262+ . catch ( async ( error : unknown ) => {
263+ if ( error instanceof MissingBundledDirectoryContractArtifactError ) {
264+ return null ;
265+ }
266+ throw error ;
267+ } )
268+ . then ( async ( loaded ) => {
269+ if ( ! loaded ) {
270+ return ( await getBundledChannelPluginAsync ( id ) ) ?? null ;
271+ }
272+ const plugin = findChannelDirectoryContractPlugin ( loaded ) ;
273+ return plugin ?? ( await getBundledChannelPluginAsync ( id ) ) ?? null ;
274+ } )
275+ . then ( ( plugin ) => {
276+ channelDirectoryPluginCache . set ( id , plugin ) ;
277+ return plugin ;
278+ } )
279+ . finally ( ( ) => {
280+ channelDirectoryPluginPromiseCache . delete ( id ) ;
281+ } ) ;
282+ channelDirectoryPluginPromiseCache . set ( id , loading ) ;
283+ return ( await loading ) ?? undefined ;
284+ }
0 commit comments