FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
opencode/packages/opencode/src/plugin/shared.ts at dev · argszero/opencode · GitHub
argszero
/
opencode
Public
forked from
anomalyco/opencode
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
0
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
opencode
/
packages
/
opencode
/
src
/
plugin
/
shared.ts
Copy path
More file actions
More file actions
Latest commit
History
History
History
323 lines (275 loc) · 9.97 KB
Breadcrumbs
opencode
/
packages
/
opencode
/
src
/
plugin
/
shared.ts
Copy path
File metadata and controls
323 lines (275 loc) · 9.97 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
import
path
from
"path"
import
{
fileURLToPath
,
pathToFileURL
}
from
"url"
import
npa
from
"npm-package-arg"
import
semver
from
"semver"
import
{
Filesystem
}
from
"@/util/filesystem"
import
{
isRecord
}
from
"@/util/record"
import
{
Npm
}
from
"@opencode-ai/core/npm"
// Old npm package names for plugins that are now built-in
export
const
DEPRECATED_PLUGIN_PACKAGES
=
[
"opencode-openai-codex-auth"
,
"opencode-copilot-auth"
]
export
function
isDeprecatedPlugin
(
spec
:
string
)
{
return
DEPRECATED_PLUGIN_PACKAGES
.
some
(
(
pkg
)
=>
spec
.
includes
(
pkg
)
)
}
function
parse
(
spec
:
string
)
{
try
{
return
npa
(
spec
)
}
catch
{
}
}
export
function
parsePluginSpecifier
(
spec
:
string
)
{
const
hit
=
parse
(
spec
)
if
(
hit
?.
type
===
"alias"
&&
!
hit
.
name
)
{
const
sub
=
(
hit
as
npa
.
AliasResult
)
.
subSpec
if
(
sub
?.
name
)
{
const
version
=
!
sub
.
rawSpec
||
sub
.
rawSpec
===
"*"
?
"latest"
:
sub
.
rawSpec
return
{
pkg
:
sub
.
name
,
version
}
}
}
if
(
!
hit
?.
name
)
return
{
pkg
:
spec
,
version
:
""
}
if
(
hit
.
raw
===
hit
.
name
)
return
{
pkg
:
hit
.
name
,
version
:
"latest"
}
return
{
pkg
:
hit
.
name
,
version
:
hit
.
rawSpec
}
}
export
type
PluginSource
=
"file"
|
"npm"
export
type
PluginKind
=
"server"
|
"tui"
type
PluginMode
=
"strict"
|
"detect"
export
type
PluginPackage
=
{
dir
:
string
pkg
:
string
json
:
Record
<
string
,
unknown
>
}
export
type
PluginEntry
=
{
spec
:
string
source
:
PluginSource
target
:
string
pkg
?:
PluginPackage
entry
?:
string
}
const
INDEX_FILES
=
[
"index.ts"
,
"index.tsx"
,
"index.js"
,
"index.mjs"
,
"index.cjs"
]
export
function
pluginSource
(
spec
:
string
)
:
PluginSource
{
if
(
isPathPluginSpec
(
spec
)
)
return
"file"
return
"npm"
}
function
resolveExportPath
(
raw
:
string
,
dir
:
string
)
{
if
(
raw
.
startsWith
(
"file://"
)
)
return
fileURLToPath
(
raw
)
if
(
path
.
isAbsolute
(
raw
)
)
return
raw
return
path
.
resolve
(
dir
,
raw
)
}
function
isAbsolutePath
(
raw
:
string
)
{
return
path
.
isAbsolute
(
raw
)
||
/
^
[
A
-
Z
a
-
z
]
:
[
\\
/
]
/
.
test
(
raw
)
}
function
extractExportValue
(
value
:
unknown
)
:
string
|
undefined
{
if
(
typeof
value
===
"string"
)
return
value
if
(
!
isRecord
(
value
)
)
return
undefined
for
(
const
key
of
[
"import"
,
"default"
]
)
{
const
nested
=
value
[
key
]
if
(
typeof
nested
===
"string"
)
return
nested
}
return
undefined
}
function
packageMain
(
pkg
:
PluginPackage
)
{
const
value
=
pkg
.
json
.
main
if
(
typeof
value
!==
"string"
)
return
const
next
=
value
.
trim
(
)
if
(
!
next
)
return
return
next
}
function
resolvePackageFile
(
spec
:
string
,
raw
:
string
,
kind
:
string
,
pkg
:
PluginPackage
)
{
const
resolved
=
resolveExportPath
(
raw
,
pkg
.
dir
)
const
root
=
Filesystem
.
resolve
(
pkg
.
dir
)
const
next
=
Filesystem
.
resolve
(
resolved
)
if
(
!
Filesystem
.
contains
(
root
,
next
)
)
{
throw
new
Error
(
`Plugin
${
spec
}
resolved
${
kind
}
entry outside plugin directory`
)
}
return
next
}
function
resolvePackagePath
(
spec
:
string
,
raw
:
string
,
kind
:
PluginKind
,
pkg
:
PluginPackage
)
{
return
pathToFileURL
(
resolvePackageFile
(
spec
,
raw
,
kind
,
pkg
)
)
.
href
}
function
resolvePackageEntrypoint
(
spec
:
string
,
kind
:
PluginKind
,
pkg
:
PluginPackage
)
{
const
exports
=
pkg
.
json
.
exports
if
(
isRecord
(
exports
)
)
{
const
raw
=
extractExportValue
(
exports
[
`./
${
kind
}
`
]
)
if
(
raw
)
return
resolvePackagePath
(
spec
,
raw
,
kind
,
pkg
)
}
if
(
kind
!==
"server"
)
return
const
main
=
packageMain
(
pkg
)
if
(
!
main
)
return
return
resolvePackagePath
(
spec
,
main
,
kind
,
pkg
)
}
function
targetPath
(
target
:
string
)
{
if
(
target
.
startsWith
(
"file://"
)
)
return
fileURLToPath
(
target
)
if
(
path
.
isAbsolute
(
target
)
)
return
target
}
async
function
resolveDirectoryIndex
(
dir
:
string
)
{
for
(
const
name
of
INDEX_FILES
)
{
const
file
=
path
.
join
(
dir
,
name
)
if
(
await
Filesystem
.
exists
(
file
)
)
return
file
}
}
async
function
resolveTargetDirectory
(
target
:
string
)
{
const
file
=
targetPath
(
target
)
if
(
!
file
)
return
const
stat
=
await
Filesystem
.
statAsync
(
file
)
if
(
!
stat
?.
isDirectory
(
)
)
return
return
file
}
async
function
resolvePluginEntrypoint
(
spec
:
string
,
target
:
string
,
kind
:
PluginKind
,
pkg
?:
PluginPackage
)
{
const
source
=
pluginSource
(
spec
)
const
hit
=
pkg
??
(
source
===
"npm"
?
await
readPluginPackage
(
target
)
:
await
readPluginPackage
(
target
)
.
catch
(
(
)
=>
undefined
)
)
if
(
!
hit
)
return
target
const
entry
=
resolvePackageEntrypoint
(
spec
,
kind
,
hit
)
if
(
entry
)
return
entry
const
dir
=
await
resolveTargetDirectory
(
target
)
if
(
kind
===
"tui"
)
{
if
(
source
===
"file"
&&
dir
)
{
const
index
=
await
resolveDirectoryIndex
(
dir
)
if
(
index
)
return
pathToFileURL
(
index
)
.
href
}
if
(
source
===
"npm"
)
return
if
(
dir
)
return
return
target
}
if
(
dir
&&
isRecord
(
hit
.
json
.
exports
)
)
{
if
(
source
===
"file"
)
{
const
index
=
await
resolveDirectoryIndex
(
dir
)
if
(
index
)
return
pathToFileURL
(
index
)
.
href
}
return
}
return
target
}
export
function
isPathPluginSpec
(
spec
:
string
)
{
return
spec
.
startsWith
(
"file://"
)
||
spec
.
startsWith
(
"."
)
||
isAbsolutePath
(
spec
)
}
export
async
function
resolvePathPluginTarget
(
spec
:
string
)
{
const
raw
=
spec
.
startsWith
(
"file://"
)
?
fileURLToPath
(
spec
)
:
spec
const
file
=
path
.
isAbsolute
(
raw
)
||
/
^
[
A
-
Z
a
-
z
]
:
[
\\
/
]
/
.
test
(
raw
)
?
raw
:
path
.
resolve
(
raw
)
const
stat
=
await
Filesystem
.
statAsync
(
file
)
if
(
!
stat
?.
isDirectory
(
)
)
{
if
(
spec
.
startsWith
(
"file://"
)
)
return
spec
return
pathToFileURL
(
file
)
.
href
}
if
(
await
Filesystem
.
exists
(
path
.
join
(
file
,
"package.json"
)
)
)
{
return
pathToFileURL
(
file
)
.
href
}
const
index
=
await
resolveDirectoryIndex
(
file
)
if
(
index
)
return
pathToFileURL
(
index
)
.
href
throw
new
Error
(
`Plugin directory
${
file
}
is missing package.json or index file`
)
}
export
async
function
checkPluginCompatibility
(
target
:
string
,
opencodeVersion
:
string
,
pkg
?:
PluginPackage
)
{
if
(
!
semver
.
valid
(
opencodeVersion
)
||
semver
.
major
(
opencodeVersion
)
===
0
)
return
const
hit
=
pkg
??
(
await
readPluginPackage
(
target
)
.
catch
(
(
)
=>
undefined
)
)
if
(
!
hit
)
return
const
engines
=
hit
.
json
.
engines
if
(
!
isRecord
(
engines
)
)
return
const
range
=
engines
.
opencode
if
(
typeof
range
!==
"string"
)
return
if
(
!
semver
.
satisfies
(
opencodeVersion
,
range
)
)
{
throw
new
Error
(
`Plugin requires opencode
${
range
}
but running
${
opencodeVersion
}
`
)
}
}
export
async
function
resolvePluginTarget
(
spec
:
string
)
{
if
(
isPathPluginSpec
(
spec
)
)
return
resolvePathPluginTarget
(
spec
)
const
hit
=
parse
(
spec
)
const
pkg
=
hit
?.
name
&&
hit
.
raw
===
hit
.
name
?
`
${
hit
.
name
}
@latest`
:
spec
const
result
=
await
Npm
.
add
(
pkg
)
return
result
.
directory
}
export
async
function
readPluginPackage
(
target
:
string
)
:
Promise
<
PluginPackage
>
{
const
file
=
target
.
startsWith
(
"file://"
)
?
fileURLToPath
(
target
)
:
target
const
stat
=
await
Filesystem
.
statAsync
(
file
)
const
dir
=
stat
?.
isDirectory
(
)
?
file
:
path
.
dirname
(
file
)
const
pkg
=
path
.
join
(
dir
,
"package.json"
)
const
json
=
await
Filesystem
.
readJson
<
Record
<
string
,
unknown
>
>
(
pkg
)
return
{
dir
,
pkg
,
json
}
}
export
async
function
createPluginEntry
(
spec
:
string
,
target
:
string
,
kind
:
PluginKind
)
:
Promise
<
PluginEntry
>
{
const
source
=
pluginSource
(
spec
)
const
pkg
=
source
===
"npm"
?
await
readPluginPackage
(
target
)
:
await
readPluginPackage
(
target
)
.
catch
(
(
)
=>
undefined
)
const
entry
=
await
resolvePluginEntrypoint
(
spec
,
target
,
kind
,
pkg
)
return
{
spec
,
source
,
target
,
pkg
,
entry
,
}
}
export
function
readPackageThemes
(
spec
:
string
,
pkg
:
PluginPackage
)
{
const
field
=
pkg
.
json
[
"oc-themes"
]
if
(
field
===
undefined
)
return
[
]
if
(
!
Array
.
isArray
(
field
)
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
has invalid oc-themes field`
)
}
const
list
=
field
.
map
(
(
item
)
=>
{
if
(
typeof
item
!==
"string"
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
has invalid oc-themes entry`
)
}
const
raw
=
item
.
trim
(
)
if
(
!
raw
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
has empty oc-themes entry`
)
}
if
(
raw
.
startsWith
(
"file://"
)
||
isAbsolutePath
(
raw
)
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
oc-themes entry must be relative:
${
item
}
`
)
}
return
resolvePackageFile
(
spec
,
raw
,
"oc-themes"
,
pkg
)
}
)
return
Array
.
from
(
new
Set
(
list
)
)
}
export
function
readPluginId
(
id
:
unknown
,
spec
:
string
)
{
if
(
id
===
undefined
)
return
if
(
typeof
id
!==
"string"
)
throw
new
TypeError
(
`Plugin
${
spec
}
has invalid id type
${
typeof
id
}
`
)
const
value
=
id
.
trim
(
)
if
(
!
value
)
throw
new
TypeError
(
`Plugin
${
spec
}
has an empty id`
)
return
value
}
export
function
readV1Plugin
(
mod
:
Record
<
string
,
unknown
>
,
spec
:
string
,
kind
:
PluginKind
,
mode
:
PluginMode
=
"strict"
,
)
{
const
value
=
mod
.
default
if
(
!
isRecord
(
value
)
)
{
if
(
mode
===
"detect"
)
return
throw
new
TypeError
(
`Plugin
${
spec
}
must default export an object with
${
kind
}
()`
)
}
if
(
mode
===
"detect"
&&
!
(
"id"
in
value
)
&&
!
(
"server"
in
value
)
&&
!
(
"tui"
in
value
)
)
return
const
server
=
"server"
in
value
?
value
.
server
:
undefined
const
tui
=
"tui"
in
value
?
value
.
tui
:
undefined
if
(
server
!==
undefined
&&
typeof
server
!==
"function"
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
has invalid server export`
)
}
if
(
tui
!==
undefined
&&
typeof
tui
!==
"function"
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
has invalid tui export`
)
}
if
(
server
!==
undefined
&&
tui
!==
undefined
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
must default export either server() or tui(), not both`
)
}
if
(
kind
===
"server"
&&
server
===
undefined
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
must default export an object with server()`
)
}
if
(
kind
===
"tui"
&&
tui
===
undefined
)
{
throw
new
TypeError
(
`Plugin
${
spec
}
must default export an object with tui()`
)
}
return
value
}
export
async
function
resolvePluginId
(
source
:
PluginSource
,
spec
:
string
,
target
:
string
,
id
:
string
|
undefined
,
pkg
?:
PluginPackage
,
)
{
if
(
source
===
"file"
)
{
if
(
id
)
return
id
throw
new
TypeError
(
`Path plugin
${
spec
}
must export id`
)
}
if
(
id
)
return
id
const
hit
=
pkg
??
(
await
readPluginPackage
(
target
)
)
if
(
typeof
hit
.
json
.
name
!==
"string"
||
!
hit
.
json
.
name
.
trim
(
)
)
{
throw
new
TypeError
(
`Plugin package
${
hit
.
pkg
}
is missing name`
)
}
return
hit
.
json
.
name
.
trim
(
)
}
Back
|
FazBrowse Home
|
New Git URL