Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/dlx/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ export interface DlxMetadata {

/**
* Clean expired entries from the DLX cache.
*
* @example
* ```typescript
* // Remove cache entries older than the default TTL
* const removed = await cleanDlxCache()
*
* // Remove entries older than 1 hour
* const removed2 = await cleanDlxCache(60 * 60 * 1000)
* ```
*/
export async function cleanDlxCache(
maxAge: number = DLX_BINARY_CACHE_TTL,
Expand Down Expand Up @@ -260,6 +269,15 @@ export async function cleanDlxCache(

/**
* Download and execute a binary from a URL with caching.
*
* @example
* ```typescript
* const result = await dlxBinary(['--version'], {
* url: 'https://example.com/tool-linux-x64',
* name: 'tool',
* })
* await result.spawnPromise
* ```
*/
export async function dlxBinary(
args: readonly string[] | string[],
Expand Down Expand Up @@ -417,6 +435,15 @@ export async function dlxBinary(
* Similar to downloadPackage from dlx-package.
*
* @returns Object containing the path to the cached binary and whether it was downloaded
*
* @example
* ```typescript
* const { binaryPath, downloaded } = await downloadBinary({
* url: 'https://example.com/tool-linux-x64',
* name: 'tool',
* })
* console.log(`Binary at: ${binaryPath}, fresh: ${downloaded}`)
* ```
*/
export async function downloadBinary(
options: Omit<DlxBinaryOptions, 'spawnOptions'>,
Expand Down Expand Up @@ -512,6 +539,15 @@ export async function downloadBinary(
* - integrity: SRI format sha512-<base64> (verified post-download)
*
* The sha256 option is preferred as it fails early during download if the checksum doesn't match.
*
* @example
* ```typescript
* const integrity = await downloadBinaryFile(
* 'https://example.com/tool-linux-x64',
* '/tmp/dlx-cache/tool'
* )
* console.log(`Integrity: ${integrity}`)
* ```
*/
export async function downloadBinaryFile(
url: string,
Expand Down Expand Up @@ -608,6 +644,15 @@ export async function downloadBinaryFile(
* @param spawnOptions Spawn options for execution
* @param spawnExtra Extra spawn configuration
* @returns The spawn promise for the running process
*
* @example
* ```typescript
* const { binaryPath } = await downloadBinary({
* url: 'https://example.com/tool-linux-x64',
* name: 'tool',
* })
* const result = executeBinary(binaryPath, ['--help'])
* ```
*/
export function executeBinary(
binaryPath: string,
Expand Down Expand Up @@ -647,20 +692,40 @@ export function executeBinary(
* Get the DLX binary cache directory path.
* Returns normalized path for cross-platform compatibility.
* Uses same directory as dlx-package for unified DLX storage.
*
* @example
* ```typescript
* const cachePath = getDlxCachePath()
* ```
*/
export function getDlxCachePath(): string {
return getSocketDlxDir()
}

/**
* Get metadata file path for a cached binary.
*
* @example
* ```typescript
* const metaPath = getBinaryCacheMetadataPath('/tmp/dlx-cache/a1b2c3d4')
* // '/tmp/dlx-cache/a1b2c3d4/.dlx-metadata.json'
* ```
*/
export function getBinaryCacheMetadataPath(cacheEntryPath: string): string {
return getPath().join(cacheEntryPath, '.dlx-metadata.json')
}

/**
* Check if a cached binary is still valid.
*
* @example
* ```typescript
* const ttl = 7 * 24 * 60 * 60 * 1000
* const valid = await isBinaryCacheValid('/tmp/dlx-cache/a1b2c3d4', ttl)
* if (!valid) {
* // Re-download the binary
* }
* ```
*/
export async function isBinaryCacheValid(
cacheEntryPath: string,
Expand Down Expand Up @@ -696,6 +761,14 @@ export async function isBinaryCacheValid(

/**
* Get information about cached binaries.
*
* @example
* ```typescript
* const entries = await listDlxCache()
* for (const entry of entries) {
* console.log(`${entry.name}: ${entry.size} bytes`)
* }
* ```
*/
export async function listDlxCache(): Promise<
Array<{
Expand Down Expand Up @@ -773,6 +846,17 @@ export async function listDlxCache(): Promise<
* Write metadata for a cached binary.
* Uses unified schema shared with C++ decompressor and CLI dlxBinary.
* Schema documentation: See DlxMetadata interface in this file (exported).
*
* @example
* ```typescript
* await writeBinaryCacheMetadata(
* '/tmp/dlx-cache/a1b2c3d4',
* 'a1b2c3d4',
* 'https://example.com/tool',
* 'sha512-abc123...',
* 15000000
* )
* ```
*/
export async function writeBinaryCacheMetadata(
cacheEntryPath: string,
Expand Down
6 changes: 6 additions & 0 deletions src/dlx/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ function getCrypto() {
* https://github.com/npm/cli/blob/v11.6.2/workspaces/libnpmexec/lib/index.js#L233-L244
* Implementation: packages.map().sort().join('\n') → SHA-512 → slice(0,16)
* npx hashes the package spec (name@version), not just name
*
* @example
* ```typescript
* const key = generateCacheKey('prettier@3.0.0')
* // e.g. 'a1b2c3d4e5f67890'
* ```
*/
export function generateCacheKey(spec: string): string {
const crypto = getCrypto()
Expand Down
33 changes: 33 additions & 0 deletions src/dlx/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ function readPackageJson(packageJsonPath: string): object | null {
*
* @param filePath - Path within DLX cache (~/.socket/_dlx/)
* @returns Detection result
*
* @example
* ```typescript
* const result = detectDlxExecutableType('/tmp/.socket/_dlx/a1b2c3d4/tool')
* console.log(result.type) // 'package' or 'binary'
* ```
*/
export function detectDlxExecutableType(
filePath: string,
Expand Down Expand Up @@ -210,6 +216,14 @@ export function detectExecutableType(
*
* @param filePath - Local filesystem path (not in DLX cache)
* @returns Detection result
*
* @example
* ```typescript
* const result = detectLocalExecutableType('/usr/local/bin/tool')
* if (result.type === 'package') {
* console.log('Node.js package at:', result.packageJsonPath)
* }
* ```
*/
export function detectLocalExecutableType(
filePath: string,
Expand Down Expand Up @@ -253,6 +267,13 @@ export function detectLocalExecutableType(
*
* @param filePath - Path to check
* @returns True if file has .js, .mjs, or .cjs extension
*
* @example
* ```typescript
* isJsFilePath('index.js') // true
* isJsFilePath('lib.mjs') // true
* isJsFilePath('tool.exe') // false
* ```
*/
export function isJsFilePath(filePath: string): boolean {
const path = getPath()
Expand All @@ -265,6 +286,12 @@ export function isJsFilePath(filePath: string): boolean {
*
* @param filePath - Path to check
* @returns True if detected as native binary (not Node.js package)
*
* @example
* ```typescript
* isNativeBinary('/usr/local/bin/tool') // true
* isNativeBinary('/tmp/project/index.js') // false
* ```
*/
export function isNativeBinary(filePath: string): boolean {
return detectExecutableType(filePath).type === 'binary'
Expand All @@ -275,6 +302,12 @@ export function isNativeBinary(filePath: string): boolean {
*
* @param filePath - Path to check
* @returns True if detected as Node.js package
*
* @example
* ```typescript
* isNodePackage('/tmp/project/index.js') // true
* isNodePackage('/usr/local/bin/tool') // false
* ```
*/
export function isNodePackage(filePath: string): boolean {
return detectExecutableType(filePath).type === 'package'
Expand Down
34 changes: 34 additions & 0 deletions src/dlx/dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ function getFs() {

/**
* Clear all DLX package installations.
*
* @example
* ```typescript
* await clearDlx()
* ```
*/
export async function clearDlx(): Promise<void> {
const packages = await listDlxPackagesAsync()
Expand All @@ -38,6 +43,11 @@ export async function clearDlx(): Promise<void> {

/**
* Clear all DLX package installations synchronously.
*
* @example
* ```typescript
* clearDlxSync()
* ```
*/
export function clearDlxSync(): void {
const packages = listDlxPackages()
Expand All @@ -48,6 +58,13 @@ export function clearDlxSync(): void {

/**
* Check if the DLX directory exists.
*
* @example
* ```typescript
* if (dlxDirExists()) {
* console.log('DLX directory is present')
* }
* ```
*/
export function dlxDirExists(): boolean {
const fs = getFs()
Expand All @@ -56,6 +73,13 @@ export function dlxDirExists(): boolean {

/**
* Check if the DLX directory exists asynchronously.
*
* @example
* ```typescript
* if (await dlxDirExistsAsync()) {
* console.log('DLX directory is present')
* }
* ```
*/
export async function dlxDirExistsAsync(): Promise<boolean> {
const fs = getFs()
Expand All @@ -69,13 +93,23 @@ export async function dlxDirExistsAsync(): Promise<boolean> {

/**
* Ensure the DLX directory exists, creating it if necessary.
*
* @example
* ```typescript
* await ensureDlxDir()
* ```
*/
export async function ensureDlxDir(): Promise<void> {
await safeMkdir(getSocketDlxDir())
}

/**
* Ensure the DLX directory exists synchronously, creating it if necessary.
*
* @example
* ```typescript
* ensureDlxDirSync()
* ```
*/
export function ensureDlxDirSync(): void {
safeMkdirSync(getSocketDlxDir())
Expand Down
16 changes: 16 additions & 0 deletions src/dlx/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ export interface DlxManifestOptions {

/**
* Type guard for binary entries.
*
* @example
* ```typescript
* const entry = manifest.getManifestEntry('https://example.com/tool')
* if (entry && isBinaryEntry(entry)) {
* console.log(entry.details.integrity)
* }
* ```
*/
export function isBinaryEntry(
entry: ManifestEntry,
Expand All @@ -133,6 +141,14 @@ export function isBinaryEntry(

/**
* Type guard for package entries.
*
* @example
* ```typescript
* const entry = manifest.getManifestEntry('@socketsecurity/cli@^2.0.0')
* if (entry && isPackageEntry(entry)) {
* console.log(entry.details.installed_version)
* }
* ```
*/
export function isPackageEntry(
entry: ManifestEntry,
Expand Down
Loading