@@ -331,6 +331,117 @@ describe("plugin.loader.shared", () => {
331331 }
332332 } )
333333
334+ test ( "loads npm server plugin from package server export without leading dot" , async ( ) => {
335+ await using tmp = await tmpdir ( {
336+ init : async ( dir ) => {
337+ const mod = path . join ( dir , "mods" , "acme-plugin" )
338+ const dist = path . join ( mod , "dist" )
339+ const mark = path . join ( dir , "server-called.txt" )
340+ await fs . mkdir ( dist , { recursive : true } )
341+
342+ await Bun . write (
343+ path . join ( mod , "package.json" ) ,
344+ JSON . stringify (
345+ {
346+ name : "acme-plugin" ,
347+ type : "module" ,
348+ exports : {
349+ "." : "./index.js" ,
350+ "./server" : "dist/server.js" ,
351+ } ,
352+ } ,
353+ null ,
354+ 2 ,
355+ ) ,
356+ )
357+ await Bun . write ( path . join ( mod , "index.js" ) , 'import "./main-throws.js"\nexport default {}\n' )
358+ await Bun . write ( path . join ( mod , "main-throws.js" ) , 'throw new Error("main loaded")\n' )
359+ await Bun . write (
360+ path . join ( dist , "server.js" ) ,
361+ [
362+ "export default {" ,
363+ " server: async () => {" ,
364+ ` await Bun.write(${ JSON . stringify ( mark ) } , "called")` ,
365+ " return {}" ,
366+ " }," ,
367+ "}" ,
368+ "" ,
369+ ] . join ( "\n" ) ,
370+ )
371+
372+ await Bun . write ( path . join ( dir , "opencode.json" ) , JSON . stringify ( { plugin : [ "acme-plugin@1.0.0" ] } , null , 2 ) )
373+
374+ return {
375+ mod,
376+ mark,
377+ }
378+ } ,
379+ } )
380+
381+ const install = spyOn ( BunProc , "install" ) . mockResolvedValue ( tmp . extra . mod )
382+
383+ try {
384+ const errors = await errs ( tmp . path )
385+ expect ( errors ) . toHaveLength ( 0 )
386+ expect ( await Bun . file ( tmp . extra . mark ) . text ( ) ) . toBe ( "called" )
387+ } finally {
388+ install . mockRestore ( )
389+ }
390+ } )
391+
392+ test ( "loads npm server plugin from package main without leading dot" , async ( ) => {
393+ await using tmp = await tmpdir ( {
394+ init : async ( dir ) => {
395+ const mod = path . join ( dir , "mods" , "acme-plugin" )
396+ const dist = path . join ( mod , "dist" )
397+ const mark = path . join ( dir , "main-called.txt" )
398+ await fs . mkdir ( dist , { recursive : true } )
399+
400+ await Bun . write (
401+ path . join ( mod , "package.json" ) ,
402+ JSON . stringify (
403+ {
404+ name : "acme-plugin" ,
405+ type : "module" ,
406+ main : "dist/index.js" ,
407+ } ,
408+ null ,
409+ 2 ,
410+ ) ,
411+ )
412+ await Bun . write (
413+ path . join ( dist , "index.js" ) ,
414+ [
415+ "export default {" ,
416+ " server: async () => {" ,
417+ ` await Bun.write(${ JSON . stringify ( mark ) } , "called")` ,
418+ " return {}" ,
419+ " }," ,
420+ "}" ,
421+ "" ,
422+ ] . join ( "\n" ) ,
423+ )
424+
425+ await Bun . write ( path . join ( dir , "opencode.json" ) , JSON . stringify ( { plugin : [ "acme-plugin@1.0.0" ] } , null , 2 ) )
426+
427+ return {
428+ mod,
429+ mark,
430+ }
431+ } ,
432+ } )
433+
434+ const install = spyOn ( BunProc , "install" ) . mockResolvedValue ( tmp . extra . mod )
435+
436+ try {
437+ const errors = await errs ( tmp . path )
438+ expect ( errors ) . toHaveLength ( 0 )
439+ expect ( await Bun . file ( tmp . extra . mark ) . text ( ) ) . toBe ( "called" )
440+ } finally {
441+ install . mockRestore ( )
442+ }
443+ } )
444+
334445 test ( "does not use npm package exports dot for server entry" , async ( ) => {
335446 await using tmp = await tmpdir ( {
336447 init : async ( dir ) => {
0 commit comments