Skip to content

Commit f2c6336

Browse files
committed
refactor(dlx): expose helpers and sort methods for better maintainability
Exposes previously private helper functions (ensurePackageInstalled, parsePackageSpec, resolveBinaryPath, makePackageBinsExecutable, downloadBinaryFile, getMetadataPath, isCacheValid, writeMetadata) to enable external usage. Reorganizes all dlx module functions alphabetically per coding standards (private functions first, then exported functions).
1 parent 6580b33 commit f2c6336

4 files changed

Lines changed: 556 additions & 550 deletions

File tree

src/dlx/binary.ts

Lines changed: 146 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -181,152 +181,6 @@ export interface DlxMetadata {
181181
}
182182
}
183183

184-
/**
185-
* Get metadata file path for a cached binary.
186-
*/
187-
function getMetadataPath(cacheEntryPath: string): string {
188-
return getPath().join(cacheEntryPath, '.dlx-metadata.json')
189-
}
190-
191-
/**
192-
* Check if a cached binary is still valid.
193-
*/
194-
async function isCacheValid(
195-
cacheEntryPath: string,
196-
cacheTtl: number,
197-
): Promise<boolean> {
198-
const fs = getFs()
199-
try {
200-
const metaPath = getMetadataPath(cacheEntryPath)
201-
if (!fs.existsSync(metaPath)) {
202-
return false
203-
}
204-
205-
const metadata = await readJson(metaPath, { throws: false })
206-
if (!isObjectObject(metadata)) {
207-
return false
208-
}
209-
const now = Date.now()
210-
const timestamp = (metadata as Record<string, unknown>)['timestamp']
211-
// If timestamp is missing or invalid, cache is invalid
212-
if (typeof timestamp !== 'number' || timestamp <= 0) {
213-
return false
214-
}
215-
const age = now - timestamp
216-
217-
return age < cacheTtl
218-
} catch {
219-
return false
220-
}
221-
}
222-
223-
/**
224-
* Download a file from a URL with integrity checking and concurrent download protection.
225-
* Uses processLock to prevent multiple processes from downloading the same binary simultaneously.
226-
* Internal helper function for downloading binary files.
227-
*/
228-
async function downloadBinaryFile(
229-
url: string,
230-
destPath: string,
231-
integrity?: string | undefined,
232-
): Promise<string> {
233-
// Use process lock to prevent concurrent downloads.
234-
// Lock is placed in the cache entry directory as 'concurrency.lock'.
235-
const crypto = getCrypto()
236-
const fs = getFs()
237-
const path = getPath()
238-
const cacheEntryDir = path.dirname(destPath)
239-
const lockPath = path.join(cacheEntryDir, 'concurrency.lock')
240-
241-
return await processLock.withLock(
242-
lockPath,
243-
async () => {
244-
// Check if file was downloaded while waiting for lock.
245-
if (fs.existsSync(destPath)) {
246-
const stats = await fs.promises.stat(destPath)
247-
if (stats.size > 0) {
248-
// File exists, compute and return SRI integrity hash.
249-
const fileBuffer = await fs.promises.readFile(destPath)
250-
const hash = crypto
251-
.createHash('sha512')
252-
.update(fileBuffer)
253-
.digest('base64')
254-
return `sha512-${hash}`
255-
}
256-
}
257-
258-
// Download the file.
259-
try {
260-
await httpDownload(url, destPath)
261-
} catch (e) {
262-
throw new Error(
263-
`Failed to download binary from ${url}\n` +
264-
`Destination: ${destPath}\n` +
265-
'Check your internet connection or verify the URL is accessible.',
266-
{ cause: e },
267-
)
268-
}
269-
270-
// Compute SRI integrity hash of downloaded file.
271-
const fileBuffer = await fs.promises.readFile(destPath)
272-
const hash = crypto
273-
.createHash('sha512')
274-
.update(fileBuffer)
275-
.digest('base64')
276-
const actualIntegrity = `sha512-${hash}`
277-
278-
// Verify integrity if provided.
279-
if (integrity && actualIntegrity !== integrity) {
280-
// Clean up invalid file.
281-
await safeDelete(destPath)
282-
throw new Error(
283-
`Integrity mismatch: expected ${integrity}, got ${actualIntegrity}`,
284-
)
285-
}
286-
287-
// Make executable on POSIX systems.
288-
if (!WIN32) {
289-
await fs.promises.chmod(destPath, 0o755)
290-
}
291-
292-
return actualIntegrity
293-
},
294-
{
295-
// Align with npm npx locking strategy.
296-
staleMs: 5000,
297-
touchIntervalMs: 2000,
298-
},
299-
)
300-
}
301-
302-
/**
303-
* Write metadata for a cached binary.
304-
* Uses unified schema shared with C++ decompressor and CLI dlxBinary.
305-
* Schema documentation: See DlxMetadata interface in this file (exported).
306-
*/
307-
async function writeMetadata(
308-
cacheEntryPath: string,
309-
cacheKey: string,
310-
url: string,
311-
integrity: string,
312-
size: number,
313-
): Promise<void> {
314-
const metaPath = getMetadataPath(cacheEntryPath)
315-
const metadata: DlxMetadata = {
316-
version: '1.0.0',
317-
cache_key: cacheKey,
318-
timestamp: Date.now(),
319-
integrity,
320-
size,
321-
source: {
322-
type: 'download',
323-
url,
324-
},
325-
}
326-
const fs = getFs()
327-
await fs.promises.writeFile(metaPath, JSON.stringify(metadata, null, 2))
328-
}
329-
330184
/**
331185
* Clean expired entries from the DLX cache.
332186
*/
@@ -626,6 +480,85 @@ export async function downloadBinary(
626480
}
627481
}
628482

483+
/**
484+
* Download a file from a URL with integrity checking and concurrent download protection.
485+
* Uses processLock to prevent multiple processes from downloading the same binary simultaneously.
486+
* Internal helper function for downloading binary files.
487+
*/
488+
export async function downloadBinaryFile(
489+
url: string,
490+
destPath: string,
491+
integrity?: string | undefined,
492+
): Promise<string> {
493+
// Use process lock to prevent concurrent downloads.
494+
// Lock is placed in the cache entry directory as 'concurrency.lock'.
495+
const crypto = getCrypto()
496+
const fs = getFs()
497+
const path = getPath()
498+
const cacheEntryDir = path.dirname(destPath)
499+
const lockPath = path.join(cacheEntryDir, 'concurrency.lock')
500+
501+
return await processLock.withLock(
502+
lockPath,
503+
async () => {
504+
// Check if file was downloaded while waiting for lock.
505+
if (fs.existsSync(destPath)) {
506+
const stats = await fs.promises.stat(destPath)
507+
if (stats.size > 0) {
508+
// File exists, compute and return SRI integrity hash.
509+
const fileBuffer = await fs.promises.readFile(destPath)
510+
const hash = crypto
511+
.createHash('sha512')
512+
.update(fileBuffer)
513+
.digest('base64')
514+
return `sha512-${hash}`
515+
}
516+
}
517+
518+
// Download the file.
519+
try {
520+
await httpDownload(url, destPath)
521+
} catch (e) {
522+
throw new Error(
523+
`Failed to download binary from ${url}\n` +
524+
`Destination: ${destPath}\n` +
525+
'Check your internet connection or verify the URL is accessible.',
526+
{ cause: e },
527+
)
528+
}
529+
530+
// Compute SRI integrity hash of downloaded file.
531+
const fileBuffer = await fs.promises.readFile(destPath)
532+
const hash = crypto
533+
.createHash('sha512')
534+
.update(fileBuffer)
535+
.digest('base64')
536+
const actualIntegrity = `sha512-${hash}`
537+
538+
// Verify integrity if provided.
539+
if (integrity && actualIntegrity !== integrity) {
540+
// Clean up invalid file.
541+
await safeDelete(destPath)
542+
throw new Error(
543+
`Integrity mismatch: expected ${integrity}, got ${actualIntegrity}`,
544+
)
545+
}
546+
547+
// Make executable on POSIX systems.
548+
if (!WIN32) {
549+
await fs.promises.chmod(destPath, 0o755)
550+
}
551+
552+
return actualIntegrity
553+
},
554+
{
555+
// Align with npm npx locking strategy.
556+
staleMs: 5000,
557+
touchIntervalMs: 2000,
558+
},
559+
)
560+
}
561+
629562
/**
630563
* Execute a cached binary without re-downloading.
631564
* Similar to executePackage from dlx-package.
@@ -680,6 +613,45 @@ export function getDlxCachePath(): string {
680613
return getSocketDlxDir()
681614
}
682615

616+
/**
617+
* Get metadata file path for a cached binary.
618+
*/
619+
export function getMetadataPath(cacheEntryPath: string): string {
620+
return getPath().join(cacheEntryPath, '.dlx-metadata.json')
621+
}
622+
623+
/**
624+
* Check if a cached binary is still valid.
625+
*/
626+
export async function isCacheValid(
627+
cacheEntryPath: string,
628+
cacheTtl: number,
629+
): Promise<boolean> {
630+
const fs = getFs()
631+
try {
632+
const metaPath = getMetadataPath(cacheEntryPath)
633+
if (!fs.existsSync(metaPath)) {
634+
return false
635+
}
636+
637+
const metadata = await readJson(metaPath, { throws: false })
638+
if (!isObjectObject(metadata)) {
639+
return false
640+
}
641+
const now = Date.now()
642+
const timestamp = (metadata as Record<string, unknown>)['timestamp']
643+
// If timestamp is missing or invalid, cache is invalid
644+
if (typeof timestamp !== 'number' || timestamp <= 0) {
645+
return false
646+
}
647+
const age = now - timestamp
648+
649+
return age < cacheTtl
650+
} catch {
651+
return false
652+
}
653+
}
654+
683655
/**
684656
* Get information about cached binaries.
685657
*/
@@ -754,3 +726,31 @@ export async function listDlxCache(): Promise<
754726

755727
return results
756728
}
729+
730+
/**
731+
* Write metadata for a cached binary.
732+
* Uses unified schema shared with C++ decompressor and CLI dlxBinary.
733+
* Schema documentation: See DlxMetadata interface in this file (exported).
734+
*/
735+
export async function writeMetadata(
736+
cacheEntryPath: string,
737+
cacheKey: string,
738+
url: string,
739+
integrity: string,
740+
size: number,
741+
): Promise<void> {
742+
const metaPath = getMetadataPath(cacheEntryPath)
743+
const metadata: DlxMetadata = {
744+
version: '1.0.0',
745+
cache_key: cacheKey,
746+
timestamp: Date.now(),
747+
integrity,
748+
size,
749+
source: {
750+
type: 'download',
751+
url,
752+
},
753+
}
754+
const fs = getFs()
755+
await fs.promises.writeFile(metaPath, JSON.stringify(metadata, null, 2))
756+
}

0 commit comments

Comments
 (0)