@socketsecurity/lib 5.5.3 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -1
- package/dist/cache-with-ttl.js +5 -1
- package/dist/dlx/binary.d.ts +47 -52
- package/dist/dlx/binary.js +129 -134
- package/dist/dlx/detect.d.ts +8 -8
- package/dist/dlx/detect.js +18 -18
- package/dist/dlx/manifest.d.ts +43 -36
- package/dist/dlx/manifest.js +114 -112
- package/dist/dlx/package.d.ts +55 -0
- package/dist/dlx/package.js +89 -79
- package/dist/env/ci.js +1 -2
- package/dist/env/rewire.d.ts +33 -22
- package/dist/env/rewire.js +20 -7
- package/dist/env/socket-cli.d.ts +24 -24
- package/dist/env/socket-cli.js +12 -12
- package/dist/env/temp-dir.d.ts +6 -6
- package/dist/env/temp-dir.js +4 -4
- package/dist/env/windows.d.ts +6 -6
- package/dist/env/windows.js +4 -4
- package/dist/external/libnpmexec.js +2 -2
- package/dist/github.js +10 -1
- package/dist/http-request.d.ts +79 -63
- package/dist/http-request.js +203 -159
- package/dist/packages/specs.js +1 -1
- package/dist/releases/github.d.ts +18 -7
- package/dist/releases/github.js +94 -92
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,100 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.7.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.7.0) - 2026-02-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **env**: Added `isInEnv()` helper function to check if an environment variable key exists, regardless of its value
|
|
13
|
+
- Returns `true` even for empty strings, `"false"`, `"0"`, etc.
|
|
14
|
+
- Follows same override resolution order as `getEnvValue()` (isolated overrides → shared overrides → process.env)
|
|
15
|
+
- Useful for detecting presence of environment variables independent of their value
|
|
16
|
+
|
|
17
|
+
- **dlx**: Added new exported helper functions
|
|
18
|
+
- `downloadBinaryFile()` - Downloads a binary file from a URL to the dlx cache directory
|
|
19
|
+
- `ensurePackageInstalled()` - Ensures an npm package is installed and cached via Arborist
|
|
20
|
+
- `getBinaryCacheMetadataPath()` - Gets the file path to dlx binary cache metadata (`.dlx-metadata.json`)
|
|
21
|
+
- `isBinaryCacheValid()` - Checks if a cached dlx binary is still valid based on TTL and timestamp
|
|
22
|
+
- `makePackageBinsExecutable()` - Makes npm package binaries executable on Unix systems
|
|
23
|
+
- `parsePackageSpec()` - Parses npm package spec strings (e.g., `pkg@1.0.0`) into name and version
|
|
24
|
+
- `resolveBinaryPath()` - Resolves the absolute path to a binary within an installed package
|
|
25
|
+
- `writeBinaryCacheMetadata()` - Writes dlx binary cache metadata with integrity, size, and source info
|
|
26
|
+
|
|
27
|
+
- **releases**: Added `createAssetMatcher()` utility function for GitHub release asset pattern matching
|
|
28
|
+
- Creates matcher functions that test strings against glob patterns, prefix/suffix, or RegExp
|
|
29
|
+
- Used for dynamic asset discovery in GitHub releases (e.g., matching platform-specific binaries)
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **env**: Updated `getCI()` to use `isInEnv()` for more accurate CI detection
|
|
34
|
+
- Now returns `true` whenever the `CI` key exists in the environment, not just when truthy
|
|
35
|
+
- Matches standard CI detection behavior where the presence of the key (not its value) indicates a CI environment
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **github**: Fixed JSON parsing crash vulnerability by adding try-catch around `JSON.parse()` in GitHub API responses
|
|
40
|
+
- Prevents crashes on malformed, incomplete, or binary responses
|
|
41
|
+
- Error messages now include the response URL for better debugging
|
|
42
|
+
|
|
43
|
+
- **dlx/binary**: Fixed clock skew vulnerabilities in cache validation
|
|
44
|
+
- Cache entries with future timestamps (clock skew) are now treated as expired
|
|
45
|
+
- Metadata writes now use atomic write-then-rename pattern to prevent corruption
|
|
46
|
+
- Added TOCTOU race protection by re-checking binary existence after metadata read
|
|
47
|
+
|
|
48
|
+
- **dlx/cache cleanup**: Fixed handling of future timestamps during cache cleanup
|
|
49
|
+
- Entries with future timestamps (due to clock skew) are now properly treated as expired
|
|
50
|
+
|
|
51
|
+
- **dlx/package**: Fixed scoped package parsing bug where `@scope/package` was incorrectly parsed
|
|
52
|
+
- Changed condition from `startsWith('@')` to `atIndex === 0` for more precise detection
|
|
53
|
+
- Fixes installation failures for scoped packages like `@socketregistry/lib`
|
|
54
|
+
|
|
55
|
+
- **cache-with-ttl**: Added clock skew detection to TTL cache
|
|
56
|
+
- Far-future `expiresAt` values (>2x TTL) are now treated as expired
|
|
57
|
+
- Protects against cache poisoning from clock skew
|
|
58
|
+
|
|
59
|
+
- **packages/specs**: Fixed unconditional `.git` truncation in Git URL parsing
|
|
60
|
+
- Now only removes `.git` suffix when URL actually ends with `.git`
|
|
61
|
+
- Prevents incorrect truncation of URLs containing `.git` in the middle
|
|
62
|
+
|
|
63
|
+
- **releases/github**: Fixed TOCTOU race condition in binary download verification
|
|
64
|
+
- Re-checks binary existence after reading version file
|
|
65
|
+
- Ensures binary is re-downloaded if missing despite version file presence
|
|
66
|
+
|
|
67
|
+
- **provenance**: Fixed incorrect package name in provenance workflow
|
|
68
|
+
- Changed from `@socketregistry/lib` to `@socketsecurity/lib`
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
## [5.6.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.6.0) - 2026-02-08
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **http-request**: Added automatic default headers for JSON and text requests
|
|
76
|
+
- `httpJson()` now automatically sets `Accept: application/json` header
|
|
77
|
+
- `httpJson()` automatically sets `Content-Type: application/json` when body is present
|
|
78
|
+
- `httpText()` now automatically sets `Accept: text/plain` header
|
|
79
|
+
- `httpText()` automatically sets `Content-Type: text/plain` when body is present
|
|
80
|
+
- User-provided headers always override defaults
|
|
81
|
+
- Simplifies API usage - no need to manually set common headers
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- **http-request**: Renamed HTTP helper functions to support all HTTP methods (BREAKING CHANGE)
|
|
86
|
+
- `httpGetJson()` → `httpJson()` - Now supports GET, POST, PUT, DELETE, PATCH, etc.
|
|
87
|
+
- `httpGetText()` → `httpText()` - Now supports all HTTP methods via `method` option
|
|
88
|
+
- Functions now accept `method` parameter in options (defaults to 'GET')
|
|
89
|
+
- More flexible API that matches modern fetch-style conventions
|
|
90
|
+
- **Migration**: Replace `httpGetJson()` calls with `httpJson()` and `httpGetText()` with `httpText()`
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- **http-request**: Fixed Content-Type header incorrectly sent with empty string body
|
|
95
|
+
- Empty string body (`""`) no longer triggers Content-Type header
|
|
96
|
+
- Changed condition from `if (body !== undefined)` to `if (body)` for semantic correctness
|
|
97
|
+
- Empty string represents "no content" and should not declare a Content-Type
|
|
98
|
+
- Affects `httpJson()` and `httpText()` functions
|
|
99
|
+
- Fixes potential API compatibility issues with servers expecting no Content-Type for empty bodies
|
|
100
|
+
- Added comprehensive test coverage for empty string edge case
|
|
101
|
+
|
|
8
102
|
## [5.5.3](https://github.com/SocketDev/socket-lib/releases/tag/v5.5.3) - 2026-01-20
|
|
9
103
|
|
|
10
104
|
### Fixed
|
|
@@ -780,7 +874,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
780
874
|
|
|
781
875
|
### Changed
|
|
782
876
|
|
|
783
|
-
- **DLX binary metadata structure**: Updated `
|
|
877
|
+
- **DLX binary metadata structure**: Updated `writeBinaryCacheMetadata()` to use unified schema with additional fields
|
|
784
878
|
- Now includes `cache_key` (first 16 chars of SHA-512 hash)
|
|
785
879
|
- Added `size` field for cached binary size
|
|
786
880
|
- Added `checksum_algorithm` field (currently "sha256")
|
package/dist/cache-with-ttl.js
CHANGED
|
@@ -54,7 +54,11 @@ function createTtlCache(options) {
|
|
|
54
54
|
return `${opts.prefix}:${key}`;
|
|
55
55
|
}
|
|
56
56
|
function isExpired(entry) {
|
|
57
|
-
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
if (entry.expiresAt > now + ttl * 2) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return now > entry.expiresAt;
|
|
58
62
|
}
|
|
59
63
|
function createMatcher(pattern) {
|
|
60
64
|
const fullPattern = buildKey(pattern);
|
package/dist/dlx/binary.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export interface DlxBinaryOptions {
|
|
|
10
10
|
*/
|
|
11
11
|
name?: string | undefined;
|
|
12
12
|
/**
|
|
13
|
-
* Expected
|
|
13
|
+
* Expected SRI integrity hash (sha512-<base64>) for verification.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
integrity?: string | undefined;
|
|
16
16
|
/**
|
|
17
17
|
* Cache TTL in milliseconds (default: 7 days).
|
|
18
18
|
*/
|
|
@@ -47,65 +47,40 @@ export interface DlxBinaryResult {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Metadata structure for cached binaries (.dlx-metadata.json).
|
|
50
|
-
* Unified schema shared across TypeScript (dlxBinary) and C++
|
|
50
|
+
* Unified schema shared across TypeScript (dlxBinary) and C++ stub extractor.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
52
|
+
* Fields:
|
|
53
53
|
* - version: Schema version (currently "1.0.0")
|
|
54
54
|
* - cache_key: First 16 chars of SHA-512 hash (matches directory name)
|
|
55
55
|
* - timestamp: Unix timestamp in milliseconds
|
|
56
|
-
* -
|
|
57
|
-
* - checksum_algorithm: "sha512" or "sha256"
|
|
58
|
-
* - platform: "darwin" | "linux" | "win32"
|
|
59
|
-
* - arch: "x64" | "arm64"
|
|
56
|
+
* - integrity: SRI hash (sha512-<base64>, aligned with npm)
|
|
60
57
|
* - size: Size of cached binary in bytes
|
|
61
58
|
* - source: Origin information
|
|
62
|
-
* - type: "download"
|
|
59
|
+
* - type: "download" | "extract" | "package"
|
|
63
60
|
* - url: Download URL (if type is "download")
|
|
64
|
-
* - path: Source binary path (if type is "
|
|
61
|
+
* - path: Source binary path (if type is "extract")
|
|
62
|
+
* - spec: Package spec (if type is "package")
|
|
63
|
+
* - update_check: Update checking metadata (optional)
|
|
64
|
+
* - last_check: Timestamp of last update check
|
|
65
|
+
* - last_notification: Timestamp of last user notification
|
|
66
|
+
* - latest_known: Latest known version string
|
|
65
67
|
*
|
|
66
|
-
*
|
|
67
|
-
* - For C++ decompression:
|
|
68
|
-
* - compressed_size: Size of compressed data in bytes
|
|
69
|
-
* - compression_algorithm: Brotli level (numeric)
|
|
70
|
-
* - compression_ratio: original_size / compressed_size
|
|
71
|
-
*
|
|
72
|
-
* Example (TypeScript download):
|
|
68
|
+
* Example:
|
|
73
69
|
* ```json
|
|
74
70
|
* {
|
|
75
71
|
* "version": "1.0.0",
|
|
76
72
|
* "cache_key": "a1b2c3d4e5f67890",
|
|
77
73
|
* "timestamp": 1730332800000,
|
|
78
|
-
* "
|
|
79
|
-
* "checksum_algorithm": "sha256",
|
|
80
|
-
* "platform": "darwin",
|
|
81
|
-
* "arch": "arm64",
|
|
74
|
+
* "integrity": "sha512-abc123base64...",
|
|
82
75
|
* "size": 15000000,
|
|
83
76
|
* "source": {
|
|
84
77
|
* "type": "download",
|
|
85
78
|
* "url": "https://example.com/binary"
|
|
86
|
-
* }
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*
|
|
90
|
-
* Example (C++ decompression):
|
|
91
|
-
* ```json
|
|
92
|
-
* {
|
|
93
|
-
* "version": "1.0.0",
|
|
94
|
-
* "cache_key": "0123456789abcdef",
|
|
95
|
-
* "timestamp": 1730332800000,
|
|
96
|
-
* "checksum": "sha512-def456...",
|
|
97
|
-
* "checksum_algorithm": "sha512",
|
|
98
|
-
* "platform": "darwin",
|
|
99
|
-
* "arch": "arm64",
|
|
100
|
-
* "size": 13000000,
|
|
101
|
-
* "source": {
|
|
102
|
-
* "type": "decompression",
|
|
103
|
-
* "path": "/usr/local/bin/socket"
|
|
104
79
|
* },
|
|
105
|
-
* "
|
|
106
|
-
* "
|
|
107
|
-
* "
|
|
108
|
-
* "
|
|
80
|
+
* "update_check": {
|
|
81
|
+
* "last_check": 1730332800000,
|
|
82
|
+
* "last_notification": 1730246400000,
|
|
83
|
+
* "latest_known": "2.1.0"
|
|
109
84
|
* }
|
|
110
85
|
* }
|
|
111
86
|
* ```
|
|
@@ -116,17 +91,19 @@ export interface DlxMetadata {
|
|
|
116
91
|
version: string;
|
|
117
92
|
cache_key: string;
|
|
118
93
|
timestamp: number;
|
|
119
|
-
|
|
120
|
-
checksum_algorithm: string;
|
|
121
|
-
platform: string;
|
|
122
|
-
arch: string;
|
|
94
|
+
integrity: string;
|
|
123
95
|
size: number;
|
|
124
96
|
source?: {
|
|
125
|
-
type: 'download' | '
|
|
97
|
+
type: 'download' | 'extract' | 'package';
|
|
126
98
|
url?: string;
|
|
127
99
|
path?: string;
|
|
100
|
+
spec?: string;
|
|
101
|
+
};
|
|
102
|
+
update_check?: {
|
|
103
|
+
last_check: number;
|
|
104
|
+
last_notification: number;
|
|
105
|
+
latest_known: string;
|
|
128
106
|
};
|
|
129
|
-
extra?: Record<string, unknown>;
|
|
130
107
|
}
|
|
131
108
|
/**
|
|
132
109
|
* Clean expired entries from the DLX cache.
|
|
@@ -146,6 +123,12 @@ export declare function downloadBinary(options: Omit<DlxBinaryOptions, 'spawnOpt
|
|
|
146
123
|
binaryPath: string;
|
|
147
124
|
downloaded: boolean;
|
|
148
125
|
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Download a file from a URL with integrity checking and concurrent download protection.
|
|
128
|
+
* Uses processLock to prevent multiple processes from downloading the same binary simultaneously.
|
|
129
|
+
* Internal helper function for downloading binary files.
|
|
130
|
+
*/
|
|
131
|
+
export declare function downloadBinaryFile(url: string, destPath: string, integrity?: string | undefined): Promise<string>;
|
|
149
132
|
/**
|
|
150
133
|
* Execute a cached binary without re-downloading.
|
|
151
134
|
* Similar to executePackage from dlx-package.
|
|
@@ -164,15 +147,27 @@ export declare function executeBinary(binaryPath: string, args: readonly string[
|
|
|
164
147
|
* Uses same directory as dlx-package for unified DLX storage.
|
|
165
148
|
*/
|
|
166
149
|
export declare function getDlxCachePath(): string;
|
|
150
|
+
/**
|
|
151
|
+
* Get metadata file path for a cached binary.
|
|
152
|
+
*/
|
|
153
|
+
export declare function getBinaryCacheMetadataPath(cacheEntryPath: string): string;
|
|
154
|
+
/**
|
|
155
|
+
* Check if a cached binary is still valid.
|
|
156
|
+
*/
|
|
157
|
+
export declare function isBinaryCacheValid(cacheEntryPath: string, cacheTtl: number): Promise<boolean>;
|
|
167
158
|
/**
|
|
168
159
|
* Get information about cached binaries.
|
|
169
160
|
*/
|
|
170
161
|
export declare function listDlxCache(): Promise<Array<{
|
|
171
162
|
age: number;
|
|
172
|
-
|
|
173
|
-
checksum: string;
|
|
163
|
+
integrity: string;
|
|
174
164
|
name: string;
|
|
175
|
-
platform: string;
|
|
176
165
|
size: number;
|
|
177
166
|
url: string;
|
|
178
167
|
}>>;
|
|
168
|
+
/**
|
|
169
|
+
* Write metadata for a cached binary.
|
|
170
|
+
* Uses unified schema shared with C++ decompressor and CLI dlxBinary.
|
|
171
|
+
* Schema documentation: See DlxMetadata interface in this file (exported).
|
|
172
|
+
*/
|
|
173
|
+
export declare function writeBinaryCacheMetadata(cacheEntryPath: string, cacheKey: string, url: string, integrity: string, size: number): Promise<void>;
|
package/dist/dlx/binary.js
CHANGED
|
@@ -22,15 +22,18 @@ __export(binary_exports, {
|
|
|
22
22
|
cleanDlxCache: () => cleanDlxCache,
|
|
23
23
|
dlxBinary: () => dlxBinary,
|
|
24
24
|
downloadBinary: () => downloadBinary,
|
|
25
|
+
downloadBinaryFile: () => downloadBinaryFile,
|
|
25
26
|
executeBinary: () => executeBinary,
|
|
27
|
+
getBinaryCacheMetadataPath: () => getBinaryCacheMetadataPath,
|
|
26
28
|
getDlxCachePath: () => getDlxCachePath,
|
|
27
|
-
|
|
29
|
+
isBinaryCacheValid: () => isBinaryCacheValid,
|
|
30
|
+
listDlxCache: () => listDlxCache,
|
|
31
|
+
writeBinaryCacheMetadata: () => writeBinaryCacheMetadata
|
|
28
32
|
});
|
|
29
33
|
module.exports = __toCommonJS(binary_exports);
|
|
30
34
|
var import_platform = require("../constants/platform");
|
|
31
35
|
var import_time = require("../constants/time");
|
|
32
36
|
var import_cache = require("./cache");
|
|
33
|
-
var import_manifest = require("./manifest");
|
|
34
37
|
var import_http_request = require("../http-request");
|
|
35
38
|
var import_fs = require("../fs");
|
|
36
39
|
var import_objects = require("../objects");
|
|
@@ -62,115 +65,6 @@ function getPath() {
|
|
|
62
65
|
}
|
|
63
66
|
return _path;
|
|
64
67
|
}
|
|
65
|
-
function getMetadataPath(cacheEntryPath) {
|
|
66
|
-
return (/* @__PURE__ */ getPath()).join(cacheEntryPath, ".dlx-metadata.json");
|
|
67
|
-
}
|
|
68
|
-
async function isCacheValid(cacheEntryPath, cacheTtl) {
|
|
69
|
-
const fs = /* @__PURE__ */ getFs();
|
|
70
|
-
try {
|
|
71
|
-
const metaPath = getMetadataPath(cacheEntryPath);
|
|
72
|
-
if (!fs.existsSync(metaPath)) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
const metadata = await (0, import_fs.readJson)(metaPath, { throws: false });
|
|
76
|
-
if (!(0, import_objects.isObjectObject)(metadata)) {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
const now = Date.now();
|
|
80
|
-
const timestamp = metadata["timestamp"];
|
|
81
|
-
if (typeof timestamp !== "number" || timestamp <= 0) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
const age = now - timestamp;
|
|
85
|
-
return age < cacheTtl;
|
|
86
|
-
} catch {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async function downloadBinaryFile(url, destPath, checksum) {
|
|
91
|
-
const crypto = /* @__PURE__ */ getCrypto();
|
|
92
|
-
const fs = /* @__PURE__ */ getFs();
|
|
93
|
-
const path = /* @__PURE__ */ getPath();
|
|
94
|
-
const cacheEntryDir = path.dirname(destPath);
|
|
95
|
-
const lockPath = path.join(cacheEntryDir, "concurrency.lock");
|
|
96
|
-
return await import_process_lock.processLock.withLock(
|
|
97
|
-
lockPath,
|
|
98
|
-
async () => {
|
|
99
|
-
if (fs.existsSync(destPath)) {
|
|
100
|
-
const stats = await fs.promises.stat(destPath);
|
|
101
|
-
if (stats.size > 0) {
|
|
102
|
-
const fileBuffer2 = await fs.promises.readFile(destPath);
|
|
103
|
-
const hasher2 = crypto.createHash("sha256");
|
|
104
|
-
hasher2.update(fileBuffer2);
|
|
105
|
-
return hasher2.digest("hex");
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
await (0, import_http_request.httpDownload)(url, destPath);
|
|
110
|
-
} catch (e) {
|
|
111
|
-
throw new Error(
|
|
112
|
-
`Failed to download binary from ${url}
|
|
113
|
-
Destination: ${destPath}
|
|
114
|
-
Check your internet connection or verify the URL is accessible.`,
|
|
115
|
-
{ cause: e }
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
const fileBuffer = await fs.promises.readFile(destPath);
|
|
119
|
-
const hasher = crypto.createHash("sha256");
|
|
120
|
-
hasher.update(fileBuffer);
|
|
121
|
-
const actualChecksum = hasher.digest("hex");
|
|
122
|
-
if (checksum && actualChecksum !== checksum) {
|
|
123
|
-
await (0, import_fs.safeDelete)(destPath);
|
|
124
|
-
throw new Error(
|
|
125
|
-
`Checksum mismatch: expected ${checksum}, got ${actualChecksum}`
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
if (!import_platform.WIN32) {
|
|
129
|
-
await fs.promises.chmod(destPath, 493);
|
|
130
|
-
}
|
|
131
|
-
return actualChecksum;
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
// Align with npm npx locking strategy.
|
|
135
|
-
staleMs: 5e3,
|
|
136
|
-
touchIntervalMs: 2e3
|
|
137
|
-
}
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
async function writeMetadata(cacheEntryPath, cacheKey, url, binaryName, checksum, size) {
|
|
141
|
-
const metaPath = getMetadataPath(cacheEntryPath);
|
|
142
|
-
const metadata = {
|
|
143
|
-
version: "1.0.0",
|
|
144
|
-
cache_key: cacheKey,
|
|
145
|
-
timestamp: Date.now(),
|
|
146
|
-
checksum,
|
|
147
|
-
checksum_algorithm: "sha256",
|
|
148
|
-
platform: (0, import_platform.getPlatform)(),
|
|
149
|
-
arch: (0, import_platform.getArch)(),
|
|
150
|
-
size,
|
|
151
|
-
source: {
|
|
152
|
-
type: "download",
|
|
153
|
-
url
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const fs = /* @__PURE__ */ getFs();
|
|
157
|
-
await fs.promises.writeFile(metaPath, JSON.stringify(metadata, null, 2));
|
|
158
|
-
try {
|
|
159
|
-
const spec = `${url}:${binaryName}`;
|
|
160
|
-
await import_manifest.dlxManifest.setBinaryEntry(spec, cacheKey, {
|
|
161
|
-
checksum,
|
|
162
|
-
checksum_algorithm: metadata.checksum_algorithm,
|
|
163
|
-
platform: metadata.platform,
|
|
164
|
-
arch: metadata.arch,
|
|
165
|
-
size,
|
|
166
|
-
source: {
|
|
167
|
-
type: "download",
|
|
168
|
-
url
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
} catch {
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
68
|
async function cleanDlxCache(maxAge = import_time.DLX_BINARY_CACHE_TTL) {
|
|
175
69
|
const cacheDir = getDlxCachePath();
|
|
176
70
|
const fs = /* @__PURE__ */ getFs();
|
|
@@ -183,7 +77,7 @@ async function cleanDlxCache(maxAge = import_time.DLX_BINARY_CACHE_TTL) {
|
|
|
183
77
|
const entries = await fs.promises.readdir(cacheDir);
|
|
184
78
|
for (const entry of entries) {
|
|
185
79
|
const entryPath = path.join(cacheDir, entry);
|
|
186
|
-
const metaPath =
|
|
80
|
+
const metaPath = getBinaryCacheMetadataPath(entryPath);
|
|
187
81
|
try {
|
|
188
82
|
if (!await (0, import_fs.isDir)(entryPath)) {
|
|
189
83
|
continue;
|
|
@@ -194,7 +88,7 @@ async function cleanDlxCache(maxAge = import_time.DLX_BINARY_CACHE_TTL) {
|
|
|
194
88
|
}
|
|
195
89
|
const timestamp = metadata["timestamp"];
|
|
196
90
|
const age = typeof timestamp === "number" && timestamp > 0 ? now - timestamp : Number.POSITIVE_INFINITY;
|
|
197
|
-
if (age > maxAge) {
|
|
91
|
+
if (age < 0 || age > maxAge) {
|
|
198
92
|
await (0, import_fs.safeDelete)(entryPath, { force: true, recursive: true });
|
|
199
93
|
cleaned += 1;
|
|
200
94
|
}
|
|
@@ -214,8 +108,8 @@ async function cleanDlxCache(maxAge = import_time.DLX_BINARY_CACHE_TTL) {
|
|
|
214
108
|
async function dlxBinary(args, options, spawnExtra) {
|
|
215
109
|
const {
|
|
216
110
|
cacheTtl = import_time.DLX_BINARY_CACHE_TTL,
|
|
217
|
-
checksum,
|
|
218
111
|
force: userForce = false,
|
|
112
|
+
integrity,
|
|
219
113
|
name,
|
|
220
114
|
spawnOptions,
|
|
221
115
|
url,
|
|
@@ -231,13 +125,16 @@ async function dlxBinary(args, options, spawnExtra) {
|
|
|
231
125
|
const cacheEntryDir = path.join(cacheDir, cacheKey);
|
|
232
126
|
const binaryPath = (0, import_normalize.normalizePath)(path.join(cacheEntryDir, binaryName));
|
|
233
127
|
let downloaded = false;
|
|
234
|
-
let
|
|
235
|
-
if (!force && fs.existsSync(cacheEntryDir) && await
|
|
128
|
+
let computedIntegrity = integrity;
|
|
129
|
+
if (!force && fs.existsSync(cacheEntryDir) && await isBinaryCacheValid(cacheEntryDir, cacheTtl)) {
|
|
236
130
|
try {
|
|
237
|
-
const metaPath =
|
|
131
|
+
const metaPath = getBinaryCacheMetadataPath(cacheEntryDir);
|
|
238
132
|
const metadata = await (0, import_fs.readJson)(metaPath, { throws: false });
|
|
239
|
-
if (metadata && typeof metadata === "object" && !Array.isArray(metadata) && typeof metadata["
|
|
240
|
-
|
|
133
|
+
if (metadata && typeof metadata === "object" && !Array.isArray(metadata) && typeof metadata["integrity"] === "string") {
|
|
134
|
+
computedIntegrity = metadata["integrity"];
|
|
135
|
+
if (!fs.existsSync(binaryPath)) {
|
|
136
|
+
downloaded = true;
|
|
137
|
+
}
|
|
241
138
|
} else {
|
|
242
139
|
downloaded = true;
|
|
243
140
|
}
|
|
@@ -271,14 +168,13 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
|
|
|
271
168
|
{ cause: e }
|
|
272
169
|
);
|
|
273
170
|
}
|
|
274
|
-
|
|
171
|
+
computedIntegrity = await downloadBinaryFile(url, binaryPath, integrity);
|
|
275
172
|
const stats = await fs.promises.stat(binaryPath);
|
|
276
|
-
await
|
|
173
|
+
await writeBinaryCacheMetadata(
|
|
277
174
|
cacheEntryDir,
|
|
278
175
|
cacheKey,
|
|
279
176
|
url,
|
|
280
|
-
|
|
281
|
-
computedChecksum || "",
|
|
177
|
+
computedIntegrity || "",
|
|
282
178
|
stats.size
|
|
283
179
|
);
|
|
284
180
|
}
|
|
@@ -301,8 +197,8 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
|
|
|
301
197
|
async function downloadBinary(options) {
|
|
302
198
|
const {
|
|
303
199
|
cacheTtl = import_time.DLX_BINARY_CACHE_TTL,
|
|
304
|
-
checksum,
|
|
305
200
|
force = false,
|
|
201
|
+
integrity,
|
|
306
202
|
name,
|
|
307
203
|
url
|
|
308
204
|
} = { __proto__: null, ...options };
|
|
@@ -315,7 +211,7 @@ async function downloadBinary(options) {
|
|
|
315
211
|
const cacheEntryDir = path.join(cacheDir, cacheKey);
|
|
316
212
|
const binaryPath = (0, import_normalize.normalizePath)(path.join(cacheEntryDir, binaryName));
|
|
317
213
|
let downloaded = false;
|
|
318
|
-
if (!force && fs.existsSync(cacheEntryDir) && await
|
|
214
|
+
if (!force && fs.existsSync(cacheEntryDir) && await isBinaryCacheValid(cacheEntryDir, cacheTtl)) {
|
|
319
215
|
downloaded = false;
|
|
320
216
|
} else {
|
|
321
217
|
try {
|
|
@@ -341,14 +237,17 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
|
|
|
341
237
|
{ cause: e }
|
|
342
238
|
);
|
|
343
239
|
}
|
|
344
|
-
const
|
|
240
|
+
const computedIntegrity = await downloadBinaryFile(
|
|
241
|
+
url,
|
|
242
|
+
binaryPath,
|
|
243
|
+
integrity
|
|
244
|
+
);
|
|
345
245
|
const stats = await fs.promises.stat(binaryPath);
|
|
346
|
-
await
|
|
246
|
+
await writeBinaryCacheMetadata(
|
|
347
247
|
cacheEntryDir,
|
|
348
248
|
cacheKey,
|
|
349
249
|
url,
|
|
350
|
-
|
|
351
|
-
computedChecksum || "",
|
|
250
|
+
computedIntegrity || "",
|
|
352
251
|
stats.size
|
|
353
252
|
);
|
|
354
253
|
downloaded = true;
|
|
@@ -358,6 +257,54 @@ Ensure the filesystem is writable or set SOCKET_DLX_DIR to a writable location.`
|
|
|
358
257
|
downloaded
|
|
359
258
|
};
|
|
360
259
|
}
|
|
260
|
+
async function downloadBinaryFile(url, destPath, integrity) {
|
|
261
|
+
const crypto = /* @__PURE__ */ getCrypto();
|
|
262
|
+
const fs = /* @__PURE__ */ getFs();
|
|
263
|
+
const path = /* @__PURE__ */ getPath();
|
|
264
|
+
const cacheEntryDir = path.dirname(destPath);
|
|
265
|
+
const lockPath = path.join(cacheEntryDir, "concurrency.lock");
|
|
266
|
+
return await import_process_lock.processLock.withLock(
|
|
267
|
+
lockPath,
|
|
268
|
+
async () => {
|
|
269
|
+
if (fs.existsSync(destPath)) {
|
|
270
|
+
const stats = await fs.promises.stat(destPath);
|
|
271
|
+
if (stats.size > 0) {
|
|
272
|
+
const fileBuffer2 = await fs.promises.readFile(destPath);
|
|
273
|
+
const hash2 = crypto.createHash("sha512").update(fileBuffer2).digest("base64");
|
|
274
|
+
return `sha512-${hash2}`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
await (0, import_http_request.httpDownload)(url, destPath);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
`Failed to download binary from ${url}
|
|
282
|
+
Destination: ${destPath}
|
|
283
|
+
Check your internet connection or verify the URL is accessible.`,
|
|
284
|
+
{ cause: e }
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
const fileBuffer = await fs.promises.readFile(destPath);
|
|
288
|
+
const hash = crypto.createHash("sha512").update(fileBuffer).digest("base64");
|
|
289
|
+
const actualIntegrity = `sha512-${hash}`;
|
|
290
|
+
if (integrity && actualIntegrity !== integrity) {
|
|
291
|
+
await (0, import_fs.safeDelete)(destPath);
|
|
292
|
+
throw new Error(
|
|
293
|
+
`Integrity mismatch: expected ${integrity}, got ${actualIntegrity}`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
if (!import_platform.WIN32) {
|
|
297
|
+
await fs.promises.chmod(destPath, 493);
|
|
298
|
+
}
|
|
299
|
+
return actualIntegrity;
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
// Align with npm npx locking strategy.
|
|
303
|
+
staleMs: 5e3,
|
|
304
|
+
touchIntervalMs: 2e3
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
}
|
|
361
308
|
function executeBinary(binaryPath, args, spawnOptions, spawnExtra) {
|
|
362
309
|
const needsShell = import_platform.WIN32 && /\.(?:bat|cmd|ps1)$/i.test(binaryPath);
|
|
363
310
|
const path = /* @__PURE__ */ getPath();
|
|
@@ -375,6 +322,34 @@ function executeBinary(binaryPath, args, spawnOptions, spawnExtra) {
|
|
|
375
322
|
function getDlxCachePath() {
|
|
376
323
|
return (0, import_socket.getSocketDlxDir)();
|
|
377
324
|
}
|
|
325
|
+
function getBinaryCacheMetadataPath(cacheEntryPath) {
|
|
326
|
+
return (/* @__PURE__ */ getPath()).join(cacheEntryPath, ".dlx-metadata.json");
|
|
327
|
+
}
|
|
328
|
+
async function isBinaryCacheValid(cacheEntryPath, cacheTtl) {
|
|
329
|
+
const fs = /* @__PURE__ */ getFs();
|
|
330
|
+
try {
|
|
331
|
+
const metaPath = getBinaryCacheMetadataPath(cacheEntryPath);
|
|
332
|
+
if (!fs.existsSync(metaPath)) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
const metadata = await (0, import_fs.readJson)(metaPath, { throws: false });
|
|
336
|
+
if (!(0, import_objects.isObjectObject)(metadata)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const now = Date.now();
|
|
340
|
+
const timestamp = metadata["timestamp"];
|
|
341
|
+
if (typeof timestamp !== "number" || timestamp <= 0) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const age = now - timestamp;
|
|
345
|
+
if (age < 0) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return age < cacheTtl;
|
|
349
|
+
} catch {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
378
353
|
async function listDlxCache() {
|
|
379
354
|
const cacheDir = getDlxCachePath();
|
|
380
355
|
const fs = /* @__PURE__ */ getFs();
|
|
@@ -391,7 +366,7 @@ async function listDlxCache() {
|
|
|
391
366
|
if (!await (0, import_fs.isDir)(entryPath)) {
|
|
392
367
|
continue;
|
|
393
368
|
}
|
|
394
|
-
const metaPath =
|
|
369
|
+
const metaPath = getBinaryCacheMetadataPath(entryPath);
|
|
395
370
|
const metadata = await (0, import_fs.readJson)(metaPath, { throws: false });
|
|
396
371
|
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
397
372
|
continue;
|
|
@@ -406,10 +381,8 @@ async function listDlxCache() {
|
|
|
406
381
|
const binaryStats = await fs.promises.stat(binaryPath);
|
|
407
382
|
results.push({
|
|
408
383
|
age: now - (metaObj["timestamp"] || 0),
|
|
409
|
-
|
|
410
|
-
checksum: metaObj["checksum"] || "",
|
|
384
|
+
integrity: metaObj["integrity"] || "",
|
|
411
385
|
name: binaryFile,
|
|
412
|
-
platform: metaObj["platform"] || "unknown",
|
|
413
386
|
size: binaryStats.size,
|
|
414
387
|
url
|
|
415
388
|
});
|
|
@@ -419,12 +392,34 @@ async function listDlxCache() {
|
|
|
419
392
|
}
|
|
420
393
|
return results;
|
|
421
394
|
}
|
|
395
|
+
async function writeBinaryCacheMetadata(cacheEntryPath, cacheKey, url, integrity, size) {
|
|
396
|
+
const metaPath = getBinaryCacheMetadataPath(cacheEntryPath);
|
|
397
|
+
const metadata = {
|
|
398
|
+
version: "1.0.0",
|
|
399
|
+
cache_key: cacheKey,
|
|
400
|
+
timestamp: Date.now(),
|
|
401
|
+
integrity,
|
|
402
|
+
size,
|
|
403
|
+
source: {
|
|
404
|
+
type: "download",
|
|
405
|
+
url
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const fs = /* @__PURE__ */ getFs();
|
|
409
|
+
const tmpPath = `${metaPath}.tmp.${process.pid}`;
|
|
410
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(metadata, null, 2));
|
|
411
|
+
await fs.promises.rename(tmpPath, metaPath);
|
|
412
|
+
}
|
|
422
413
|
// Annotate the CommonJS export names for ESM import in node:
|
|
423
414
|
0 && (module.exports = {
|
|
424
415
|
cleanDlxCache,
|
|
425
416
|
dlxBinary,
|
|
426
417
|
downloadBinary,
|
|
418
|
+
downloadBinaryFile,
|
|
427
419
|
executeBinary,
|
|
420
|
+
getBinaryCacheMetadataPath,
|
|
428
421
|
getDlxCachePath,
|
|
429
|
-
|
|
422
|
+
isBinaryCacheValid,
|
|
423
|
+
listDlxCache,
|
|
424
|
+
writeBinaryCacheMetadata
|
|
430
425
|
});
|