Skip to content

Commit 1d62ff6

Browse files
committed
feat(dlx): align cache key generation with npm/npx pattern
- Use SHA-512 truncated to 16 chars instead of full SHA-256 (64 chars) - Construct spec from url:binaryName for unique cache identity - Update all tests to match new hash algorithm and spec format - Aligns with npm/npx ecosystem for Windows MAX_PATH compatibility Reference: npm/cli v11.6.2 libnpmexec/lib/index.js#L233-L244
1 parent a266143 commit 1d62ff6

2 files changed

Lines changed: 47 additions & 17 deletions

File tree

src/dlx-binary.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,31 @@ export interface DlxBinaryResult {
4040
}
4141

4242
/**
43-
* Generate a cache directory name from URL and binary name.
44-
* Uses SHA256 hash to create content-addressed storage.
45-
* Includes binary name to prevent collisions when multiple binaries
46-
* are downloaded from the same URL with different names.
43+
* Generate a cache directory name using npm/npx approach.
44+
* Uses first 16 characters of SHA-512 hash (like npm/npx).
45+
*
46+
* Rationale for SHA-512 truncated (vs full SHA-256):
47+
* - Matches npm/npx ecosystem behavior
48+
* - Shorter paths for Windows MAX_PATH compatibility (260 chars)
49+
* - 16 hex chars = 64 bits = acceptable collision risk for local cache
50+
* - Collision probability ~1 in 18 quintillion with 1000 entries
51+
*
52+
* Input strategy (aligned with npx):
53+
* - npx uses package spec strings (e.g., '@scope/pkg@1.0.0', 'prettier@3.0.0')
54+
* - Caller provides complete spec string with version for accurate cache keying
55+
* - For package installs: Use PURL-style spec with version
56+
* Examples: 'npm:prettier@3.0.0', 'pypi:requests@2.31.0', 'gem:rails@7.0.0'
57+
* Note: Socket uses shorthand format without 'pkg:' prefix
58+
* (handled by @socketregistry/packageurl-js)
59+
* - For binary downloads: Use URL for uniqueness
60+
*
61+
* Reference: npm/cli v11.6.2 libnpmexec/lib/index.js#L233-L244
62+
* https://github.com/npm/cli/blob/v11.6.2/workspaces/libnpmexec/lib/index.js#L233-L244
63+
* Implementation: packages.map().sort().join('\n') → SHA-512 → slice(0,16)
64+
* npx hashes the package spec (name@version), not just name
4765
*/
48-
function generateCacheKey(url: string, name: string): string {
49-
return createHash('sha256').update(`${url}:${name}`).digest('hex')
66+
function generateCacheKey(spec: string): string {
67+
return createHash('sha512').update(spec).digest('hex').substring(0, 16)
5068
}
5169

5270
/**
@@ -234,7 +252,9 @@ export async function dlxBinary(
234252
// Generate cache paths similar to pnpm/npx structure.
235253
const cacheDir = getDlxCachePath()
236254
const binaryName = name || `binary-${process.platform}-${os.arch()}`
237-
const cacheKey = generateCacheKey(url, binaryName)
255+
// Create spec from URL and binary name for unique cache identity.
256+
const spec = `${url}:${binaryName}`
257+
const cacheKey = generateCacheKey(spec)
238258
const cacheEntryDir = path.join(cacheDir, cacheKey)
239259
const binaryPath = normalizePath(path.join(cacheEntryDir, binaryName))
240260

test/dlx-binary.test.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,11 @@ describe.sequential('dlx-binary', () => {
368368

369369
// Corrupt metadata
370370
const name = 'invalid-meta-binary'
371-
const cacheKey = createHash('sha256')
372-
.update(`${url}:${name}`)
371+
const spec = `${url}:${name}`
372+
const cacheKey = createHash('sha512')
373+
.update(spec)
373374
.digest('hex')
375+
.substring(0, 16)
374376
const cachePath = getDlxCachePath()
375377
const metaPath = path.join(cachePath, cacheKey, '.dlx-metadata.json')
376378
await fs.writeFile(metaPath, 'invalid json', 'utf8')
@@ -405,9 +407,11 @@ describe.sequential('dlx-binary', () => {
405407

406408
// Delete metadata
407409
const name = 'missing-meta-binary'
408-
const cacheKey = createHash('sha256')
409-
.update(`${url}:${name}`)
410+
const spec = `${url}:${name}`
411+
const cacheKey = createHash('sha512')
412+
.update(spec)
410413
.digest('hex')
414+
.substring(0, 16)
411415
const cachePath = getDlxCachePath()
412416
const metaPath = path.join(cachePath, cacheKey, '.dlx-metadata.json')
413417
await fs.unlink(metaPath)
@@ -442,9 +446,11 @@ describe.sequential('dlx-binary', () => {
442446

443447
// Write array as metadata (invalid)
444448
const name = 'array-meta-binary'
445-
const cacheKey = createHash('sha256')
446-
.update(`${url}:${name}`)
449+
const spec = `${url}:${name}`
450+
const cacheKey = createHash('sha512')
451+
.update(spec)
447452
.digest('hex')
453+
.substring(0, 16)
448454
const cachePath = getDlxCachePath()
449455
const metaPath = path.join(cachePath, cacheKey, '.dlx-metadata.json')
450456
await fs.writeFile(metaPath, JSON.stringify([]), 'utf8')
@@ -479,9 +485,11 @@ describe.sequential('dlx-binary', () => {
479485

480486
// Write metadata without checksum
481487
const name = 'no-checksum-meta-binary'
482-
const cacheKey = createHash('sha256')
483-
.update(`${url}:${name}`)
488+
const spec = `${url}:${name}`
489+
const cacheKey = createHash('sha512')
490+
.update(spec)
484491
.digest('hex')
492+
.substring(0, 16)
485493
const cachePath = getDlxCachePath()
486494
const metaPath = path.join(cachePath, cacheKey, '.dlx-metadata.json')
487495
await fs.writeFile(
@@ -1240,9 +1248,11 @@ describe.sequential('dlx-binary', () => {
12401248

12411249
// Make metadata unreadable (change permissions)
12421250
const name = 'read-error-binary'
1243-
const cacheKey = createHash('sha256')
1244-
.update(`${url}:${name}`)
1251+
const spec = `${url}:${name}`
1252+
const cacheKey = createHash('sha512')
1253+
.update(spec)
12451254
.digest('hex')
1255+
.substring(0, 16)
12461256
const cachePath = getDlxCachePath()
12471257
const metaPath = path.join(cachePath, cacheKey, '.dlx-metadata.json')
12481258

0 commit comments

Comments
 (0)