@@ -268,9 +268,18 @@ export function getAuthHeaders(): Record<string, string> {
268268
269269/**
270270 * Pattern for matching release assets.
271- * Can be either a prefix/suffix pair or a RegExp.
271+ * Can be either:
272+ * - A string with wildcard (*) for simple glob patterns (e.g., 'yoga-sync-*.mjs')
273+ * - A prefix/suffix pair for explicit matching
274+ * - A RegExp for complex patterns
275+ *
276+ * String patterns support a single wildcard (*) which matches any characters:
277+ * - 'yoga-sync-*.mjs' → prefix: 'yoga-sync-', suffix: '.mjs'
278+ * - 'models-*.tar.gz' → prefix: 'models-', suffix: '.tar.gz'
279+ * - '*-models.tar.gz' → prefix: '', suffix: '-models.tar.gz'
280+ * - 'yoga-*' → prefix: 'yoga-', suffix: ''
272281 */
273- export type AssetPattern = { prefix : string ; suffix : string } | RegExp
282+ export type AssetPattern = string | { prefix : string ; suffix : string } | RegExp
274283
275284/**
276285 * Result of finding a release asset.
@@ -282,30 +291,102 @@ export interface FindReleaseAssetResult {
282291 assetName : string
283292}
284293
294+ /**
295+ * Parse a wildcard pattern string into prefix/suffix components.
296+ * Supports a single wildcard (*) character.
297+ *
298+ * @param pattern - Pattern string with optional wildcard (e.g., 'yoga-sync-*.mjs')
299+ * @returns Prefix/suffix pair for matching
300+ * @throws Error if pattern contains multiple wildcards
301+ *
302+ * @example
303+ * ```ts
304+ * parseWildcardPattern('yoga-sync-*.mjs')
305+ * // Returns: { prefix: 'yoga-sync-', suffix: '.mjs' }
306+ *
307+ * parseWildcardPattern('models-*.tar.gz')
308+ * // Returns: { prefix: 'models-', suffix: '.tar.gz' }
309+ *
310+ * parseWildcardPattern('*-models.tar.gz')
311+ * // Returns: { prefix: '', suffix: '-models.tar.gz' }
312+ *
313+ * parseWildcardPattern('yoga-*')
314+ * // Returns: { prefix: 'yoga-', suffix: '' }
315+ *
316+ * parseWildcardPattern('exact-name.txt')
317+ * // Returns: { prefix: 'exact-name.txt', suffix: '' } (exact match)
318+ * ```
319+ */
320+ function parseWildcardPattern ( pattern : string ) : {
321+ prefix : string
322+ suffix : string
323+ } {
324+ const wildcardIndex = pattern . indexOf ( '*' )
325+
326+ // No wildcard - treat as exact match (prefix only).
327+ if ( wildcardIndex === - 1 ) {
328+ return { prefix : pattern , suffix : '' }
329+ }
330+
331+ // Check for multiple wildcards.
332+ const lastWildcardIndex = pattern . lastIndexOf ( '*' )
333+ if ( wildcardIndex !== lastWildcardIndex ) {
334+ throw new Error (
335+ `Pattern contains multiple wildcards: ${ pattern } . Only single wildcard (*) is supported.` ,
336+ )
337+ }
338+
339+ // Split at wildcard position.
340+ const prefix = pattern . slice ( 0 , wildcardIndex )
341+ const suffix = pattern . slice ( wildcardIndex + 1 )
342+
343+ return { prefix, suffix }
344+ }
345+
285346/**
286347 * Find a release asset matching a pattern in the latest release.
287348 * Searches for the first release matching the tool prefix,
288349 * then finds the first asset matching the provided pattern.
289350 *
290351 * @param toolPrefix - Tool name prefix to search for (e.g., 'yoga-layout-')
291- * @param assetPattern - Pattern to match asset names (prefix/suffix or RegExp)
352+ * @param assetPattern - Pattern to match asset names (string with wildcard, prefix/suffix object, or RegExp)
292353 * @param repoConfig - Repository configuration (owner/repo)
293354 * @param options - Additional options
294355 * @returns Result with tag and asset name, or null if not found
295356 *
296357 * @example
297358 * ```ts
298- * // Find yoga-sync asset with timestamped name
359+ * // Find yoga-sync asset with wildcard pattern
299360 * const result = await findReleaseAsset(
300361 * 'yoga-layout-',
301- * { prefix: 'yoga-sync-', suffix: ' .mjs' } ,
362+ * 'yoga-sync-* .mjs',
302363 * { owner: 'SocketDev', repo: 'socket-btm' }
303364 * )
304365 * // result = { tag: 'yoga-layout-2024-01-15-abc123', assetName: 'yoga-sync-2024-01-15-abc123.mjs' }
305366 * ```
306367 *
307368 * @example
308369 * ```ts
370+ * // Find models tar.gz with wildcard pattern
371+ * const result = await findReleaseAsset(
372+ * 'models-',
373+ * 'models-*.tar.gz',
374+ * { owner: 'SocketDev', repo: 'socket-btm' }
375+ * )
376+ * ```
377+ *
378+ * @example
379+ * ```ts
380+ * // Find asset with object pattern (backward compatible)
381+ * const result = await findReleaseAsset(
382+ * 'yoga-layout-',
383+ * { prefix: 'yoga-sync-', suffix: '.mjs' },
384+ * { owner: 'SocketDev', repo: 'socket-btm' }
385+ * )
386+ * ```
387+ *
388+ * @example
389+ * ```ts
309390 * // Find models tar.gz with regex
310391 * const result = await findReleaseAsset(
311392 * 'models-',
@@ -323,6 +404,14 @@ export async function findReleaseAsset(
323404 const { owner, repo } = repoConfig
324405 const { quiet = false } = options
325406
407+ // Normalize string patterns to prefix/suffix objects.
408+ let normalizedPattern : { prefix : string ; suffix : string } | RegExp
409+ if ( typeof assetPattern === 'string' ) {
410+ normalizedPattern = parseWildcardPattern ( assetPattern )
411+ } else {
412+ normalizedPattern = assetPattern
413+ }
414+
326415 return await pRetry (
327416 async ( ) => {
328417 // Fetch recent releases (100 should cover all tool releases).
@@ -349,12 +438,12 @@ export async function findReleaseAsset(
349438 // Find matching asset in this release.
350439 let matchingAsset : { name : string } | undefined
351440
352- if ( assetPattern instanceof RegExp ) {
441+ if ( normalizedPattern instanceof RegExp ) {
353442 matchingAsset = assets . find ( ( a : { name : string } ) =>
354- assetPattern . test ( a . name ) ,
443+ normalizedPattern . test ( a . name ) ,
355444 )
356445 } else {
357- const { prefix, suffix } = assetPattern
446+ const { prefix, suffix } = normalizedPattern
358447 matchingAsset = assets . find (
359448 ( a : { name : string } ) =>
360449 a . name . startsWith ( prefix ) && a . name . endsWith ( suffix ) ,
0 commit comments