@@ -8,181 +8,181 @@ import { Flock } from "@opencode-ai/shared/util/flock"
88
99import { parsePluginSpecifier , pluginSource } from "./shared"
1010
11- export namespace PluginMeta {
12- type Source = "file" | "npm"
13-
14- export type Theme = {
15- src : string
16- dest : string
17- mtime ?: number
18- size ?: number
19- }
11+ type Source = "file" | "npm"
2012
21- export type Entry = {
22- id : string
23- source : Source
24- spec : string
25- target : string
26- requested ?: string
27- version ?: string
28- modified ?: number
29- first_time : number
30- last_time : number
31- time_changed : number
32- load_count : number
33- fingerprint : string
34- themes ?: Record < string , Theme >
35- }
13+ export type Theme = {
14+ src : string
15+ dest : string
16+ mtime ?: number
17+ size ?: number
18+ }
3619
37- export type State = "first" | "updated" | "same"
20+ export type Entry = {
21+ id : string
22+ source : Source
23+ spec : string
24+ target : string
25+ requested ?: string
26+ version ?: string
27+ modified ?: number
28+ first_time : number
29+ last_time : number
30+ time_changed : number
31+ load_count : number
32+ fingerprint : string
33+ themes ?: Record < string , Theme >
34+ }
3835
39- export type Touch = {
40- spec : string
41- target : string
42- id : string
43- }
36+ export type State = "first" | "updated" | "same"
4437
45- type Store = Record < string , Entry >
46- type Core = Omit < Entry , "first_time" | "last_time" | "time_changed" | "load_count" | "fingerprint" | "themes" >
47- type Row = Touch & { core : Core }
38+ export type Touch = {
39+ spec : string
40+ target : string
41+ id : string
42+ }
4843
49- function storePath ( ) {
50- return Flag . OPENCODE_PLUGIN_META_FILE ?? path . join ( Global . Path . state , "plugin-meta.json" )
51- }
44+ type Store = Record < string , Entry >
45+ type Core = Omit < Entry , "first_time" | "last_time" | "time_changed" | "load_count" | "fingerprint" | "themes" >
46+ type Row = Touch & { core : Core }
5247
53- function lock ( file : string ) {
54- return ` plugin-meta: ${ file } `
55- }
48+ function storePath ( ) {
49+ return Flag . OPENCODE_PLUGIN_META_FILE ?? path . join ( Global . Path . state , " plugin-meta.json" )
50+ }
5651
57- function fileTarget ( spec : string , target : string ) {
58- if ( spec . startsWith ( "file://" ) ) return fileURLToPath ( spec )
59- if ( target . startsWith ( "file://" ) ) return fileURLToPath ( target )
60- return
61- }
52+ function lock ( file : string ) {
53+ return `plugin-meta:${ file } `
54+ }
6255
63- async function modifiedAt ( file : string ) {
64- const stat = await Filesystem . statAsync ( file )
65- if ( ! stat ) return
66- const mtime = stat . mtimeMs
67- return Math . floor ( typeof mtime === "bigint" ? Number ( mtime ) : mtime )
68- }
56+ function fileTarget ( spec : string , target : string ) {
57+ if ( spec . startsWith ( "file://" ) ) return fileURLToPath ( spec )
58+ if ( target . startsWith ( "file://" ) ) return fileURLToPath ( target )
59+ return
60+ }
6961
70- function resolvedTarget ( target : string ) {
71- if ( target . startsWith ( "file://" ) ) return fileURLToPath ( target )
72- return target
73- }
62+ async function modifiedAt ( file : string ) {
63+ const stat = await Filesystem . statAsync ( file )
64+ if ( ! stat ) return
65+ const mtime = stat . mtimeMs
66+ return Math . floor ( typeof mtime === "bigint" ? Number ( mtime ) : mtime )
67+ }
7468
75- async function npmVersion ( target : string ) {
76- const resolved = resolvedTarget ( target )
77- const stat = await Filesystem . statAsync ( resolved )
78- const dir = stat ?. isDirectory ( ) ? resolved : path . dirname ( resolved )
79- return Filesystem . readJson < { version ?: string } > ( path . join ( dir , "package.json" ) )
80- . then ( ( item ) => item . version )
81- . catch ( ( ) => undefined )
82- }
69+ function resolvedTarget ( target : string ) {
70+ if ( target . startsWith ( "file://" ) ) return fileURLToPath ( target )
71+ return target
72+ }
8373
84- async function entryCore ( item : Touch ) : Promise < Core > {
85- const spec = item . spec
86- const target = item . target
87- const source = pluginSource ( spec )
88- if ( source === "file" ) {
89- const file = fileTarget ( spec , target )
90- return {
91- id : item . id ,
92- source,
93- spec,
94- target,
95- modified : file ? await modifiedAt ( file ) : undefined ,
96- }
97- }
74+ async function npmVersion ( target : string ) {
75+ const resolved = resolvedTarget ( target )
76+ const stat = await Filesystem . statAsync ( resolved )
77+ const dir = stat ?. isDirectory ( ) ? resolved : path . dirname ( resolved )
78+ return Filesystem . readJson < { version ?: string } > ( path . join ( dir , "package.json" ) )
79+ . then ( ( item ) => item . version )
80+ . catch ( ( ) => undefined )
81+ }
9882
83+ async function entryCore ( item : Touch ) : Promise < Core > {
84+ const spec = item . spec
85+ const target = item . target
86+ const source = pluginSource ( spec )
87+ if ( source === "file" ) {
88+ const file = fileTarget ( spec , target )
9989 return {
10090 id : item . id ,
10191 source,
10292 spec,
10393 target,
104- requested : parsePluginSpecifier ( spec ) . version ,
105- version : await npmVersion ( target ) ,
94+ modified : file ? await modifiedAt ( file ) : undefined ,
10695 }
10796 }
10897
109- function fingerprint ( value : Core ) {
110- if ( value . source === "file" ) return [ value . target , value . modified ?? "" ] . join ( "|" )
111- return [ value . target , value . requested ?? "" , value . version ?? "" ] . join ( "|" )
98+ return {
99+ id : item . id ,
100+ source,
101+ spec,
102+ target,
103+ requested : parsePluginSpecifier ( spec ) . version ,
104+ version : await npmVersion ( target ) ,
112105 }
106+ }
113107
114- async function read ( file : string ) : Promise < Store > {
115- return Filesystem . readJson < Store > ( file ) . catch ( ( ) => ( { } ) as Store )
116- }
108+ function fingerprint ( value : Core ) {
109+ if ( value . source === "file" ) return [ value . target , value . modified ?? "" ] . join ( "|" )
110+ return [ value . target , value . requested ?? "" , value . version ?? "" ] . join ( "|" )
111+ }
117112
118- async function row ( item : Touch ) : Promise < Row > {
119- return {
120- ...item ,
121- core : await entryCore ( item ) ,
122- }
123- }
113+ async function read ( file : string ) : Promise < Store > {
114+ return Filesystem . readJson < Store > ( file ) . catch ( ( ) => ( { } ) as Store )
115+ }
124116
125- function next ( prev : Entry | undefined , core : Core , now : number ) : { state : State ; entry : Entry } {
126- const entry : Entry = {
127- ...core ,
128- first_time : prev ?. first_time ?? now ,
129- last_time : now ,
130- time_changed : prev ?. time_changed ?? now ,
131- load_count : ( prev ?. load_count ?? 0 ) + 1 ,
132- fingerprint : fingerprint ( core ) ,
133- themes : prev ?. themes ,
134- }
135- const state : State = ! prev ? "first" : prev . fingerprint === entry . fingerprint ? "same" : "updated"
136- if ( state === "updated" ) entry . time_changed = now
137- return {
138- state,
139- entry,
140- }
117+ async function row ( item : Touch ) : Promise < Row > {
118+ return {
119+ ...item ,
120+ core : await entryCore ( item ) ,
141121 }
122+ }
142123
143- export async function touchMany ( items : Touch [ ] ) : Promise < Array < { state : State ; entry : Entry } > > {
144- if ( ! items . length ) return [ ]
145- const file = storePath ( )
146- const rows = await Promise . all ( items . map ( ( item ) => row ( item ) ) )
147-
148- return Flock . withLock ( lock ( file ) , async ( ) => {
149- const store = await read ( file )
150- const now = Date . now ( )
151- const out : Array < { state : State ; entry : Entry } > = [ ]
152- for ( const item of rows ) {
153- const hit = next ( store [ item . id ] , item . core , now )
154- store [ item . id ] = hit . entry
155- out . push ( hit )
156- }
157- await Filesystem . writeJson ( file , store )
158- return out
159- } )
124+ function next ( prev : Entry | undefined , core : Core , now : number ) : { state : State ; entry : Entry } {
125+ const entry : Entry = {
126+ ...core ,
127+ first_time : prev ?. first_time ?? now ,
128+ last_time : now ,
129+ time_changed : prev ?. time_changed ?? now ,
130+ load_count : ( prev ?. load_count ?? 0 ) + 1 ,
131+ fingerprint : fingerprint ( core ) ,
132+ themes : prev ?. themes ,
133+ }
134+ const state : State = ! prev ? "first" : prev . fingerprint === entry . fingerprint ? "same" : "updated"
135+ if ( state === "updated" ) entry . time_changed = now
136+ return {
137+ state,
138+ entry,
160139 }
140+ }
161141
162- export async function touch ( spec : string , target : string , id : string ) : Promise < { state : State ; entry : Entry } > {
163- return touchMany ( [ { spec, target, id } ] ) . then ( ( item ) => {
164- const hit = item [ 0 ]
165- if ( hit ) return hit
166- throw new Error ( "Failed to touch plugin metadata." )
167- } )
168- }
142+ export async function touchMany ( items : Touch [ ] ) : Promise < Array < { state : State ; entry : Entry } > > {
143+ if ( ! items . length ) return [ ]
144+ const file = storePath ( )
145+ const rows = await Promise . all ( items . map ( ( item ) => row ( item ) ) )
146+
147+ return Flock . withLock ( lock ( file ) , async ( ) => {
148+ const store = await read ( file )
149+ const now = Date . now ( )
150+ const out : Array < { state : State ; entry : Entry } > = [ ]
151+ for ( const item of rows ) {
152+ const hit = next ( store [ item . id ] , item . core , now )
153+ store [ item . id ] = hit . entry
154+ out . push ( hit )
155+ }
156+ await Filesystem . writeJson ( file , store )
157+ return out
158+ } )
159+ }
169160
170- export async function setTheme ( id : string , name : string , theme : Theme ) : Promise < void > {
171- const file = storePath ( )
172- await Flock . withLock ( lock ( file ) , async ( ) => {
173- const store = await read ( file )
174- const entry = store [ id ]
175- if ( ! entry ) return
176- entry . themes = {
177- ...entry . themes ,
178- [ name ] : theme ,
179- }
180- await Filesystem . writeJson ( file , store )
181- } )
182- }
161+ export async function touch ( spec : string , target : string , id : string ) : Promise < { state : State ; entry : Entry } > {
162+ return touchMany ( [ { spec, target, id } ] ) . then ( ( item ) => {
163+ const hit = item [ 0 ]
164+ if ( hit ) return hit
165+ throw new Error ( "Failed to touch plugin metadata." )
166+ } )
167+ }
183168
184- export async function list ( ) : Promise < Store > {
185- const file = storePath ( )
186- return Flock . withLock ( lock ( file ) , async ( ) => read ( file ) )
187- }
169+ export async function setTheme ( id : string , name : string , theme : Theme ) : Promise < void > {
170+ const file = storePath ( )
171+ await Flock . withLock ( lock ( file ) , async ( ) => {
172+ const store = await read ( file )
173+ const entry = store [ id ]
174+ if ( ! entry ) return
175+ entry . themes = {
176+ ...entry . themes ,
177+ [ name ] : theme ,
178+ }
179+ await Filesystem . writeJson ( file , store )
180+ } )
188181}
182+
183+ export async function list ( ) : Promise < Store > {
184+ const file = storePath ( )
185+ return Flock . withLock ( lock ( file ) , async ( ) => read ( file ) )
186+ }
187+
188+ export * as PluginMeta from "./meta"
0 commit comments