Skip to content

Advanced

This section covers advanced usage scenarios and customization options for ts-pkgx.

Pantry-Based Package Fetching

ts-pkgx uses a pantry-based approach for fetching package information. This provides more reliable and comprehensive metadata than traditional web scraping.

Pantry Management

bash
# Download and extract the latest pantry
pkgx-tools update-pantry --pantry-dir ./custom-pantry

# Generate constants from local pantry
pkgx-tools generate-consts --source pantry --pantry-dir ./custom-pantry

# Generate constants from S3 registry (alternative)
pkgx-tools generate-consts --source registry --validate

Advanced Fetching Options

typescript
import { fetchPantryPackageWithMetadata } from 'ts-pkgx'

// Custom configuration for complex packages
const result = await fetchPantryPackageWithMetadata('rust-lang.org', {
  timeout: 120000, // 2 minutes
  debug: true, // Save screenshots for debugging
  cache: true,
  cacheExpirationMinutes: 30, // 30 minutes cache
})

if (result) {
  console.log(result.packageInfo)
}

Debug Mode

When enabled, debug mode saves screenshots and additional debugging information:

bash
# CLI debug mode
pkgx-tools fetch rust-lang.org --debug --verbose

# API debug mode
const result = await fetchPantryPackageWithMetadata('rust-lang.org', { debug: true })

Screenshots and debug information are saved in the debug directory.

Enhanced Package Generation

ts-pkgx generates packages with comprehensive JSDoc documentation and intelligent naming conventions. Each package includes rich type hints and documentation links.

JSDoc Documentation Features

Every generated package includes detailed JSDoc comments:

typescript
/**
 * Bun - Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
 *
 * **Programs:** bun
 *
 * **Install:** `pkgx bun.sh`
 *
 * **Homepage:** https://bun.sh
 *
 * **GitHub:** https://github.com/oven-sh/bun
 *
 * @see https://ts-pkgx.netlify.app/packages/bunsh
 */
export const bunPackage = {
  /**
   * List of available versions of this package
   * From newest version to oldest.
   * @see https://ts-pkgx.netlify.app/usage
   */
  versions: ['1.2.15', '1.2.14',] as const,
  // ... other properties with comprehensive JSDoc
}

Alias-Based Variable Naming

Packages with aliases use the primary alias for variable naming:

typescript
// Packages with aliases use alias-based naming
export const bunPackage = { ... }      // For bun.sh (alias: 'bun')
export const nodePackage = { ... }     // For nodejs.org (alias: 'node')
export const pythonPackage = { ... }   // For python.org (alias: 'python')

// Packages without aliases use domain-based naming
export const examplecomPackage = { ... } // For example.com (no alias)

Custom Package Transformation

You can implement custom transformation logic when processing packages:

typescript
import type { PkgxPackage } from 'ts-pkgx'
import { fetchPantryPackageWithMetadata } from 'ts-pkgx'

async function fetchAndTransform(packageName: string): Promise<PkgxPackage | null> {
  const result = await fetchPantryPackageWithMetadata(packageName)

  if (!result) return null

  // Add custom metadata
  const enhancedPackage: PkgxPackage = {
    ...result.packageInfo,
    description: `${result.packageInfo.description} [Enhanced]`,
    // Add custom fields or modify existing ones
    customCategory: determineCategory(result.packageInfo),
  }

  return enhancedPackage
}

function determineCategory(pkg: PkgxPackage): string {
  if (pkg.domain.includes('lang'))
    return 'programming-language'
  if (pkg.programs.some(p => p.includes('server')))
    return 'server'
  return 'utility'
}

Custom Output Formats

ts-pkgx generates TypeScript or JSON files by default, but you can customize the output format:

typescript
import type { PkgxPackage } from 'ts-pkgx'
import fs from 'node:fs'
import path from 'node:path'
import { fetchPantryPackageWithMetadata } from 'ts-pkgx'

async function saveAsMarkdown(packageName: string, outputDir: string): Promise<void> {
  const result = await fetchPantryPackageWithMetadata(packageName)

  if (!result) {
    throw new Error(`Package ${packageName} not found`)
  }

  const mdContent = generateMarkdown(result.packageInfo)
  const fileName = `${result.packageInfo.domain.replace(/\./g, '-')}.md`
  const filePath = path.join(outputDir, fileName)

  fs.writeFileSync(filePath, mdContent)
  console.log(`Saved markdown to ${filePath}`)
}

function generateMarkdown(pkg: PkgxPackage): string {
  return `# ${pkg.name} (${pkg.domain})

${pkg.description}

## Installation

\`\`\`bash
${pkg.installCommand}
\`\`\`

## Programs

${pkg.programs.map(p => `- ${p}`).join('\n')}

## Dependencies

${pkg.dependencies.length ? pkg.dependencies.map(d => `- ${d}`).join('\n') : 'No dependencies'}

## Links

- [Homepage](${pkg.homepageUrl || 'N/A'})
- [GitHub](${pkg.githubUrl || 'N/A'})
- [Package YAML](${pkg.packageYmlUrl || 'N/A'})
`
}

Extending the Aliases System

You can extend the built-in package aliases system:

typescript
import { aliases as PACKAGE_ALIASES } from 'ts-pkgx'

// Add custom aliases
function extendAliases(customAliases: Record<string, string>): void {
  Object.entries(customAliases).forEach(([alias, domain]) => {
    if (!PACKAGE_ALIASES[alias]) {
      PACKAGE_ALIASES[alias] = domain
      console.log(`Added alias: ${alias} -> ${domain}`)
    }
  })
}

// Usage
extendAliases({
  js: 'nodejs.org',
  py: 'python.org',
  rs: 'rust-lang.org',
  bunjs: 'bun.sh',
})

Custom Error Handling

Implement custom error handling for package fetching:

typescript
import { fetchPantryPackageWithMetadata } from 'ts-pkgx'

async function fetchWithRetry(packageName: string, maxRetries = 3): Promise<any> {
  let lastError

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      console.log(`Attempt ${attempt} of ${maxRetries} for ${packageName}`)
      return await fetchPantryPackageWithMetadata(packageName, {
        timeout: 30000 * attempt, // Increase timeout with each retry
      })
    }
    catch (error) {
      lastError = error
      console.error(`Attempt ${attempt} failed for ${packageName}:`, error.message)

      // Don't retry for certain errors
      if (error.message.includes('not found') || error.message.includes('Not Found')) {
        throw new Error(`Package ${packageName} not found`)
      }

      // Wait before retrying
      if (attempt < maxRetries) {
        const delay = 2000 * attempt // Increasing delay
        console.log(`Waiting ${delay}ms before retry...`)
        await new Promise(resolve => setTimeout(resolve, delay))
      }
    }
  }

  throw new Error(`Failed to fetch ${packageName} after ${maxRetries} attempts: ${lastError.message}`)
}

Custom Package Index Generation

Customize the generated index.ts file:

typescript
import fs from 'node:fs'
import path from 'node:path'
import { convertDomainToVarName } from 'ts-pkgx/tools/domainUtils'

function generateCustomIndex(packagesDir: string, outputFile: string): void {
  const files = fs.readdirSync(packagesDir)
    .filter(file => file.endsWith('.ts') && file !== 'index.ts' && file !== 'fetch.ts')

  const imports = files.map((file) => {
    const moduleName = file.replace(/\.ts$/, '')
    const packageVarName = `${moduleName}Package`
    return `import { ${packageVarName} } from './${moduleName}'`
  })

  const categoryMap: Record<string, string[]> = {
    languages: ['nodejs.org', 'python.org', 'rust-lang.org', 'ruby-lang.org'],
    runtimes: ['bun.sh', 'deno.land'],
    tools: ['git-crypt', 'example.com'],
  }

  let content = `${imports.join('\n')}\n\n`
  content += `export * from './fetch'\n\n`
  content += `// Package collections by category\n`

  Object.entries(categoryMap).forEach(([category, domains]) => {
    content += `export const ${category}Packages = {\n`
    domains.forEach((domain) => {
      const varName = convertDomainToVarName(domain)
      content += `  '${domain}': ${varName}Package,\n`
    })
    content += `}\n\n`
  })

  // Add the regular pantry export
  content += `export const pantry: Record<string, PkgxPackage> = {}\n\n`

  // Add standard getPackage function
  content += `/**
 * Get a package by name, supporting both full domain and aliases
 */
export function getPackage(name: string): PkgxPackage | undefined {
  // Direct lookup
  if (pantry[name]) {
    return pantry[name]
  }

  // Check aliases - loop through all packages
  for (const pkg of Object.values(pantry)) {
    // Check if this package has the name as an alias
    if (pkg.aliases && pkg.aliases.includes(name)) {
      return pkg
    }
  }

  return undefined
}\n`

  fs.writeFileSync(outputFile, content)
  console.log(`Generated custom index at ${outputFile}`)
}

Advanced Caching Strategies

ts-pkgx provides sophisticated caching capabilities for optimal performance:

typescript
import fs from 'node:fs'
import path from 'node:path'

interface CacheEntry {
  timestamp: number
  expiresAt: number
  data: any
}

// Custom cache implementation
class AdvancedCache {
  constructor(private cacheDir: string, private defaultTTL: number = 3600000) {
    if (!fs.existsSync(cacheDir)) {
      fs.mkdirSync(cacheDir, { recursive: true })
    }
  }

  save(key: string, data: any, ttl?: number): void {
    const cacheDuration = ttl || this.defaultTTL
    const cacheEntry: CacheEntry = {
      timestamp: Date.now(),
      expiresAt: Date.now() + cacheDuration,
      data,
    }

    const cacheFile = path.join(this.cacheDir, `${key}.json`)
    fs.writeFileSync(cacheFile, JSON.stringify(cacheEntry, null, 2))
  }

  load(key: string): any | null {
    const cacheFile = path.join(this.cacheDir, `${key}.json`)

    if (!fs.existsSync(cacheFile)) {
      return null
    }

    try {
      const cacheEntry: CacheEntry = JSON.parse(fs.readFileSync(cacheFile, 'utf8'))

      if (Date.now() > cacheEntry.expiresAt) {
        // Cache expired, remove file
        fs.unlinkSync(cacheFile)
        return null
      }

      return cacheEntry.data
    } catch (error) {
      // Corrupted cache file, remove it
      fs.unlinkSync(cacheFile)
      return null
    }
  }

  clear(): void {
    const files = fs.readdirSync(this.cacheDir)
    files.forEach(file => {
      if (file.endsWith('.json')) {
        fs.unlinkSync(path.join(this.cacheDir, file))
      }
    })
  }
}

// Usage
const cache = new AdvancedCache('.cache/advanced', 7200000) // 2 hours TTL

async function fetchWithAdvancedCache(packageName: string) {
  // Try cache first
  const cached = cache.load(packageName)
  if (cached) {
    console.log(`Cache hit for ${packageName}`)
    return cached
  }

  // Fetch fresh data
  const result = await fetchPantryPackageWithMetadata(packageName)
  if (result) {
    cache.save(packageName, result.packageInfo)
  }

  return result?.packageInfo
}

Optimized Batch Processing

ts-pkgx implements optimized batch processing to efficiently handle large numbers of packages:

typescript
import { fetchAndSaveAllPackages } from 'ts-pkgx'

async function optimizedBatchProcessing() {
  // Process packages with fine-tuned settings
  const packages = await fetchAndSaveAllPackages({
    concurrency: 12,        // Higher concurrency for faster processing
    timeout: 60000,         // Longer timeout for complex packages
    cacheExpirationMinutes: 30, // Shorter cache for fresher data
    limit: 100,             // Process first 100 packages for testing
    debug: false,           // Disable debug for performance
    outputJson: false,      // Generate TypeScript files
  })

  console.log(`Successfully processed ${packages.length} packages`)
  return packages
}

Batch Size Considerations

The optimal settings depend on several factors:

bash
# High-performance setup (good network, powerful machine)
pkgx-tools fetch --all --concurrency 15 --timeout 30000

# Conservative setup (slower network or machine)
pkgx-tools fetch --all --concurrency 4 --timeout 120000

# Testing setup (quick validation)
pkgx-tools fetch --all --limit 20 --concurrency 8 --verbose

Resource Management

Control memory usage and prevent resource leaks:

typescript
import { cleanupBrowserResources } from 'ts-pkgx'

async function processPackagesWithCleanup() {
  try {
    // Your package processing operations
    const result = await fetchAndSaveAllPackages({
      concurrency: 8,
      timeout: 60000,
    })

    return result
  } finally {
    // Always cleanup browser resources
    await cleanupBrowserResources()
  }
}

Custom CLI Integration

Create custom CLI tools that integrate with ts-pkgx:

typescript
#!/usr/bin/env node
import { CAC } from 'cac'
import { fetchPantryPackageWithMetadata, generateDocs } from 'ts-pkgx'

const cli = new CAC('my-custom-tool')

cli
  .command('sync <packages...>', 'Sync specific packages')
  .option('--output <dir>', 'Output directory', { default: './synced-packages' })
  .action(async (packages: string[], options) => {
    console.log(`Syncing ${packages.length} packages to ${options.output}`)

    for (const pkg of packages) {
      try {
        const result = await fetchPantryPackageWithMetadata(pkg, {
          timeout: 60000,
          cache: true,
          cacheExpirationMinutes: 60,
        })

        if (result) {
          console.log(`✅ Synced ${pkg}`)
        } else {
          console.log(`❌ Failed to sync ${pkg}`)
        }
      } catch (error) {
        console.error(`Error syncing ${pkg}:`, error.message)
      }
    }
  })

cli
  .command('docs', 'Generate documentation')
  .option('--output <dir>', 'Output directory', { default: './docs' })
  .action(async (options) => {
    await generateDocs(options.output)
    console.log(`Documentation generated in ${options.output}`)
  })

cli.parse()

Environment-Specific Configuration

Configure ts-pkgx for different environments:

typescript
interface EnvironmentConfig {
  timeout: number
  concurrency: number
  cacheExpiration: number
  debug: boolean
}

const configs: Record<string, EnvironmentConfig> = {
  development: {
    timeout: 120000,
    concurrency: 4,
    cacheExpiration: 30, // 30 minutes
    debug: true,
  },
  production: {
    timeout: 60000,
    concurrency: 12,
    cacheExpiration: 1440, // 24 hours
    debug: false,
  },
  ci: {
    timeout: 180000,
    concurrency: 8,
    cacheExpiration: 0, // No cache in CI
    debug: false,
  },
}

function getConfig(): EnvironmentConfig {
  const env = process.env.NODE_ENV || 'development'
  return configs[env] || configs.development
}

// Usage
const config = getConfig()
const result = await fetchPantryPackageWithMetadata('node', {
  timeout: config.timeout,
  debug: config.debug,
  cacheExpirationMinutes: config.cacheExpiration,
})

Released under the MIT License.