@@ -72,23 +72,6 @@ type Operation =
7272 readonly target : string
7373 }
7474
75- type Candidate =
76- | {
77- readonly type : "definition"
78- readonly definition : Plugin
79- }
80- | {
81- readonly type : "package"
82- readonly specifier : string
83- readonly options : Record < string , unknown >
84- readonly mtime ?: number
85- }
86-
87- type ConfiguredPackage = {
88- readonly operation : Extract < Operation , { type : "add" } >
89- enabled : boolean
90- }
91-
9275function parse ( input : ConfigPlugin . Plugin ) : Operation {
9376 if ( typeof input !== "string" ) {
9477 return { type : "add" , target : input . package , options : input . options ?? { } }
@@ -109,13 +92,14 @@ const scan = Effect.fn("PluginSupervisor.scan")(function* (entries: readonly Con
10992 . filter ( ( entry ) : entry is Config . Document => entry . type === "document" )
11093 . flatMap ( ( entry ) =>
11194 ( entry . info . plugins ?? [ ] ) . map ( parse ) . map ( ( operation ) => {
95+ if ( operation . type === "remove" ) return operation
11296 const directory = entry . path ? path . dirname ( entry . path ) : location . directory
11397 const target = operation . target . startsWith ( "file://" )
11498 ? fileURLToPath ( operation . target )
11599 : operation . target . startsWith ( "./" ) || operation . target . startsWith ( "../" )
116100 ? path . resolve ( directory , operation . target )
117101 : operation . target
118- return operation . type === "add" ? { ...operation , target } : { type : "remove" as const , target }
102+ return { ...operation , target }
119103 } ) ,
120104 )
121105 // Explicit config is applied last so it can remove auto-discovered packages.
@@ -136,88 +120,66 @@ const resolve = Effect.fn("PluginSupervisor.resolve")(function* (
136120 post : readonly Plugin [ ] ,
137121 operations : readonly Operation [ ] ,
138122) {
139- const plan = apply ( pre , post , operations )
140- return yield * load ( plan )
141- } )
142-
143- function apply ( pre : readonly Plugin [ ] , post : readonly Plugin [ ] , operations : readonly Operation [ ] ) {
144123 const matches = ( selector : string , target : string ) =>
145124 selector === "*" || ( selector . endsWith ( ".*" ) ? target . startsWith ( selector . slice ( 0 , - 1 ) ) : selector === target )
146- const plugins = [ ...pre , ...post ]
147- const enabled = new Set ( plugins . map ( ( plugin ) => plugin . id ) )
148- const packages = new Map < string , ConfiguredPackage > ( )
125+ const definitions = [ ...pre , ...post ]
126+ const enabled = new Set ( definitions . map ( ( plugin ) => plugin . id ) )
127+ const packages = new Map < string , Plugin > ( )
128+ const plugins = ( ) => [ ...definitions , ...packages . values ( ) ]
149129
150130 for ( const operation of operations ) {
151131 if ( operation . type === "remove" ) {
152- plugins . filter ( ( plugin ) => matches ( operation . target , plugin . id ) ) . forEach ( ( plugin ) => enabled . delete ( plugin . id ) )
153- packages . forEach ( ( item , target ) => {
154- if ( matches ( operation . target , target ) ) item . enabled = false
155- } )
132+ plugins ( )
133+ . filter ( ( plugin ) => matches ( operation . target , plugin . id ) )
134+ . forEach ( ( plugin ) => enabled . delete ( plugin . id ) )
156135 continue
157136 }
158137
159- const matched = plugins . filter ( ( plugin ) => matches ( operation . target , plugin . id ) )
160- const selectsDefinitions =
138+ const matched = plugins ( ) . filter ( ( plugin ) => matches ( operation . target , plugin . id ) )
139+ const selectsPlugins =
161140 matched . length > 0 ||
162141 operation . target === "*" ||
163142 operation . target . endsWith ( ".*" ) ||
164143 operation . target . startsWith ( "opencode." )
165- if ( selectsDefinitions ) {
144+ if ( selectsPlugins ) {
166145 matched . forEach ( ( plugin ) => enabled . add ( plugin . id ) )
167- packages . forEach ( ( item , target ) => {
168- if ( matches ( operation . target , target ) ) item . enabled = true
169- } )
170146 continue
171147 }
172148
173- packages . set ( operation . target , { operation, enabled : true } )
149+ const plugin = yield * load ( operation ) . pipe ( Effect . catchCause ( ( ) => Effect . succeed ( undefined ) ) )
150+ if ( ! plugin ) continue
151+ const previous = packages . get ( operation . target )
152+ if ( previous ) enabled . delete ( previous . id )
153+ packages . set ( operation . target , plugin )
154+ enabled . add ( plugin . id )
174155 }
175156
176- const definitions : Candidate [ ] = pre . flatMap ( ( definition ) =>
177- enabled . has ( definition . id ) ? [ { type : "definition" , definition } ] : [ ] ,
178- )
179- const configured : Candidate [ ] = Array . from ( packages . values ( ) ) . flatMap ( ( item ) =>
180- item . enabled
181- ? [
182- {
183- type : "package" ,
184- specifier : item . operation . target ,
185- options : item . operation . options ,
186- ...( item . operation . mtime === undefined ? { } : { mtime : item . operation . mtime } ) ,
187- } ,
188- ]
189- : [ ] ,
190- )
191- const posts : Candidate [ ] = post . flatMap ( ( definition ) =>
192- enabled . has ( definition . id ) ? [ { type : "definition" , definition } ] : [ ] ,
193- )
194- return [ ...definitions , ...configured , ...posts ]
195- }
157+ return [
158+ ...pre . filter ( ( plugin ) => enabled . has ( plugin . id ) ) ,
159+ ...Array . from ( packages . values ( ) ) . filter ( ( plugin ) => enabled . has ( plugin . id ) ) ,
160+ ...post . filter ( ( plugin ) => enabled . has ( plugin . id ) ) ,
161+ ]
162+ } )
196163
197- const load = Effect . fn ( "PluginSupervisor.load" ) ( function * ( plan : readonly Candidate [ ] ) {
198- return yield * Effect . forEach ( plan , ( candidate ) => {
199- if ( candidate . type === "definition" ) return Effect . succeed ( candidate . definition )
200- return Effect . gen ( function * ( ) {
201- const npm = yield * Npm . Service
202- const entrypoint = path . isAbsolute ( candidate . specifier )
203- ? pathToFileURL ( candidate . specifier ) . href
204- : ( yield * npm . add ( candidate . specifier ) ) . entrypoint
205- if ( ! entrypoint ) return undefined
206- // Bun currently ignores query parameters when caching file:// imports.
207- const source =
208- candidate . mtime === undefined
209- ? entrypoint
210- : `${ candidate . specifier . replaceAll ( "\\" , "/" ) } ?mtime=${ candidate . mtime } `
211- yield * Effect . log ( { msg : "loading plugin" , id : candidate . specifier , entrypoint : source } )
212- const mod = yield * Effect . promise ( ( ) => import ( source ) )
213- const value = ( yield * Schema . decodeUnknownEffect ( PluginModule ) ( mod ) ) . default
214- const plugin = "effect" in value ? value : PluginPromise . fromPromise ( value )
215- return {
216- id : plugin . id ,
217- effect : ( host ) => plugin . effect ( { ...host , options : candidate . options } ) ,
218- } satisfies Plugin
219- } ) . pipe ( Effect . catchCause ( ( ) => Effect . succeed ( undefined ) ) )
220- } ) . pipe ( Effect . map ( ( plugins ) => plugins . filter ( ( plugin ) => plugin !== undefined ) ) )
164+ const load = Effect . fn ( "PluginSupervisor.load" ) ( function * ( operation : Extract < Operation , { type : "add" } > ) {
165+ const npm = yield * Npm . Service
166+ const entrypoint = path . isAbsolute ( operation . target )
167+ ? pathToFileURL ( operation . target ) . href
168+ : ( yield * npm . add ( operation . target ) ) . entrypoint
169+ if ( ! entrypoint ) return
170+ // Bun currently ignores query parameters when caching file:// imports.
171+ const source =
172+ operation . mtime === undefined
173+ ? entrypoint
174+ : `${ operation . target . replaceAll ( "\\" , "/" ) } ?mtime=${ operation . mtime } `
175+ yield * Effect . log ( { msg : "loading plugin" , id : operation . target , entrypoint : source } )
176+ const mod = yield * Effect . promise ( ( ) => import ( source ) )
177+ const value = ( yield * Schema . decodeUnknownEffect ( PluginModule ) ( mod ) ) . default
178+ const plugin = "effect" in value ? value : PluginPromise . fromPromise ( value )
179+ return {
180+ id : plugin . id ,
181+ effect : ( host ) => plugin . effect ( { ...host , options : operation . options } ) ,
182+ } satisfies Plugin
221183} )
222184
223185function discoverDirectory ( fs : FSUtil . Interface , directory : string ) {
0 commit comments