1010 */
1111
1212import { XMLParser , XMLBuilder } from 'fast-xml-parser' ;
13- import type JSZip from 'jszip' ;
14- import { getZipEntriesWithPassword , resolveGridsetPasswordFromEnv } from './password' ;
13+ import { getZipEntriesFromAdapter , resolveGridsetPasswordFromEnv } from './password' ;
14+ import { openZipFromInput } from '../../utils/zip' ;
15+ import { getNodeRequire , isNodeRuntime } from '../../utils/io' ;
1516import { decodeText } from '../../utils/io' ;
1617
1718/**
@@ -134,60 +135,57 @@ export async function extractWordlists(
134135 const wordlists = new Map < string , WordList > ( ) ;
135136 const parser = new XMLParser ( ) ;
136137
137- let zip : JSZip ;
138138 try {
139- // eslint-disable-next-line @typescript-eslint/no-var-requires
140- const JSZip = require ( 'jszip' ) as typeof import ( 'jszip' ) ;
141- zip = await JSZip . loadAsync ( gridsetBuffer ) ;
142- } catch ( error : any ) {
143- throw new Error ( `Invalid gridset buffer: ${ error . message } ` ) ;
144- }
145- const entries = getZipEntriesWithPassword ( zip , password ) ;
139+ const { zip } = await openZipFromInput ( gridsetBuffer ) ;
140+ const entries = getZipEntriesFromAdapter ( zip , password ) ;
146141
147- // Process each grid file
148- for ( const entry of entries ) {
149- if ( entry . entryName . startsWith ( 'Grids/' ) && entry . entryName . endsWith ( 'grid.xml' ) ) {
150- try {
151- const xmlContent = decodeText ( await entry . getData ( ) ) ;
152- const data = parser . parse ( xmlContent ) ;
153- const grid = data . Grid || data . grid ;
142+ // Process each grid file
143+ for ( const entry of entries ) {
144+ if ( entry . entryName . startsWith ( 'Grids/' ) && entry . entryName . endsWith ( 'grid.xml' ) ) {
145+ try {
146+ const xmlContent = decodeText ( await entry . getData ( ) ) ;
147+ const data = parser . parse ( xmlContent ) ;
148+ const grid = data . Grid || data . grid ;
154149
155- if ( ! grid || ! grid . WordList ) {
156- continue ;
157- }
150+ if ( ! grid || ! grid . WordList ) {
151+ continue ;
152+ }
158153
159- // Extract grid name from path (e.g., "Grids/MyGrid/grid.xml" -> "MyGrid")
160- const match = entry . entryName . match ( / ^ G r i d s \/ ( [ ^ / ] + ) \/ / ) ;
161- const gridName = match ? match [ 1 ] : entry . entryName ;
154+ // Extract grid name from path (e.g., "Grids/MyGrid/grid.xml" -> "MyGrid")
155+ const match = entry . entryName . match ( / ^ G r i d s \/ ( [ ^ / ] + ) \/ / ) ;
156+ const gridName = match ? match [ 1 ] : entry . entryName ;
162157
163- // Parse wordlist items
164- const wordlistData = grid . WordList ;
165- const itemsContainer = wordlistData . Items || wordlistData . items ;
158+ // Parse wordlist items
159+ const wordlistData = grid . WordList ;
160+ const itemsContainer = wordlistData . Items || wordlistData . items ;
166161
167- if ( ! itemsContainer ) {
168- continue ;
169- }
162+ if ( ! itemsContainer ) {
163+ continue ;
164+ }
170165
171- const itemArray = Array . isArray ( itemsContainer . WordListItem )
172- ? itemsContainer . WordListItem
173- : itemsContainer . WordListItem
174- ? [ itemsContainer . WordListItem ]
175- : [ ] ;
166+ const itemArray = Array . isArray ( itemsContainer . WordListItem )
167+ ? itemsContainer . WordListItem
168+ : itemsContainer . WordListItem
169+ ? [ itemsContainer . WordListItem ]
170+ : [ ] ;
176171
177- const items : WordListItem [ ] = itemArray . map ( ( item : any ) => ( {
178- text : item . Text ?. s ?. r || item . text ?. s ?. r || '' ,
179- image : item . Image || item . image || undefined ,
180- partOfSpeech : item . PartOfSpeech || item . partOfSpeech || 'Unknown' ,
181- } ) ) ;
172+ const items : WordListItem [ ] = itemArray . map ( ( item : any ) => ( {
173+ text : item . Text ?. s ?. r || item . text ?. s ?. r || '' ,
174+ image : item . Image || item . image || undefined ,
175+ partOfSpeech : item . PartOfSpeech || item . partOfSpeech || 'Unknown' ,
176+ } ) ) ;
182177
183- if ( items . length > 0 ) {
184- wordlists . set ( gridName , { items } ) ;
178+ if ( items . length > 0 ) {
179+ wordlists . set ( gridName , { items } ) ;
180+ }
181+ } catch ( error ) {
182+ // Skip grids with parsing errors
183+ console . warn ( `Failed to extract wordlist from ${ entry . entryName } :` , error ) ;
185184 }
186- } catch ( error ) {
187- // Skip grids with parsing errors
188- console . warn ( `Failed to extract wordlist from ${ entry . entryName } :` , error ) ;
189185 }
190186 }
187+ } catch ( error : any ) {
188+ throw new Error ( `Invalid gridset buffer: ${ error . message } ` ) ;
191189 }
192190
193191 return wordlists ;
@@ -221,15 +219,50 @@ export async function updateWordlist(
221219 suppressEmptyNode : false ,
222220 } ) ;
223221
224- let zip : JSZip ;
222+ let entries : Array < {
223+ entryName : string ;
224+ getData : ( ) => Promise < Uint8Array > ;
225+ } > ;
226+ let saveZip : ( ( ) => Promise < Uint8Array > ) | null = null ;
227+ let updateEntry : ( ( entryName : string , xml : string ) => void ) | null = null ;
228+
225229 try {
226- // eslint-disable-next-line @typescript-eslint/no-var-requires
227- const JSZip = require ( 'jszip' ) as typeof import ( 'jszip' ) ;
228- zip = await JSZip . loadAsync ( gridsetBuffer ) ;
230+ if ( isNodeRuntime ( ) ) {
231+ const AdmZip = getNodeRequire ( ) ( 'adm-zip' ) as typeof import ( 'adm-zip' ) ;
232+ const zip = new AdmZip ( Buffer . from ( gridsetBuffer ) ) ;
233+ entries = zip . getEntries ( ) . map ( ( entry ) => ( {
234+ entryName : entry . entryName ,
235+ getData : ( ) => Promise . resolve ( entry . getData ( ) ) ,
236+ } ) ) ;
237+ updateEntry = ( entryName : string , xml : string ) => {
238+ zip . addFile ( entryName , Buffer . from ( xml , 'utf8' ) ) ;
239+ } ;
240+ saveZip = ( ) => Promise . resolve ( zip . toBuffer ( ) ) ;
241+ } else {
242+ const module = await import ( 'jszip' ) ;
243+ const JSZip = module . default || module ;
244+ const zip = await JSZip . loadAsync ( gridsetBuffer ) ;
245+ entries = getZipEntriesFromAdapter (
246+ {
247+ listFiles : ( ) => Object . keys ( zip . files ) ,
248+ readFile : async ( name : string ) => {
249+ const file = zip . file ( name ) ;
250+ if ( ! file ) {
251+ throw new Error ( `Zip entry not found: ${ name } ` ) ;
252+ }
253+ return file . async ( 'uint8array' ) ;
254+ } ,
255+ } ,
256+ password
257+ ) ;
258+ updateEntry = ( entryName : string , xml : string ) => {
259+ zip . file ( entryName , xml , { binary : false } ) ;
260+ } ;
261+ saveZip = async ( ) => zip . generateAsync ( { type : 'uint8array' } ) ;
262+ }
229263 } catch ( error : any ) {
230264 throw new Error ( `Invalid gridset buffer: ${ error . message } ` ) ;
231265 }
232- const entries = getZipEntriesWithPassword ( zip , password ) ;
233266
234267 let found = false ;
235268
@@ -272,7 +305,9 @@ export async function updateWordlist(
272305
273306 // Rebuild the XML
274307 const updatedXml = builder . build ( data ) ;
275- zip . file ( entry . entryName , updatedXml , { binary : false } ) ;
308+ if ( updateEntry ) {
309+ updateEntry ( entry . entryName , updatedXml ) ;
310+ }
276311 found = true ;
277312 } catch ( error ) {
278313 const message = error instanceof Error ? error . message : String ( error ) ;
@@ -286,5 +321,8 @@ export async function updateWordlist(
286321 throw new Error ( `Grid "${ gridName } " not found in gridset` ) ;
287322 }
288323
289- return await zip . generateAsync ( { type : 'uint8array' } ) ;
324+ if ( ! saveZip ) {
325+ throw new Error ( 'Failed to serialize updated gridset.' ) ;
326+ }
327+ return await saveZip ( ) ;
290328}
0 commit comments