@socketsecurity/lib 5.1.4 → 5.2.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 +15 -0
- package/dist/releases/github.d.ts +179 -0
- package/dist/releases/github.js +258 -0
- package/dist/releases/socket-btm.d.ts +221 -0
- package/dist/releases/socket-btm.js +131 -0
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ 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.2.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.2.0) - 2026-01-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **releases**: Added GitHub release download utilities for cross-project use
|
|
13
|
+
- Added `downloadGitHubRelease()` for downloading releases from any GitHub repository
|
|
14
|
+
- Added `downloadSocketBtmRelease()` specialized wrapper for socket-btm releases
|
|
15
|
+
- Features version caching with `.version` files to avoid redundant downloads
|
|
16
|
+
- Supports cross-platform binary downloads (darwin, linux, win32) with automatic platform/arch detection
|
|
17
|
+
- Includes Linux musl/glibc support with musl as default for broader compatibility
|
|
18
|
+
- Automatically removes macOS quarantine attributes from downloaded binaries
|
|
19
|
+
- Supports generic asset downloads (WASM files, models, etc.)
|
|
20
|
+
- API inspired by industry tools: `brew`, `cargo`, `gh` for intuitive usage
|
|
21
|
+
- Package exports: `@socketsecurity/lib/releases/github` and `@socketsecurity/lib/releases/socket-btm`
|
|
22
|
+
|
|
8
23
|
## [5.1.4](https://github.com/SocketDev/socket-lib/releases/tag/v5.1.4) - 2025-12-30
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket-btm GitHub repository configuration.
|
|
3
|
+
*/
|
|
4
|
+
export declare const SOCKET_BTM_REPO: {
|
|
5
|
+
readonly owner: "SocketDev";
|
|
6
|
+
readonly repo: "socket-btm";
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for repository access.
|
|
10
|
+
*/
|
|
11
|
+
export interface RepoConfig {
|
|
12
|
+
/**
|
|
13
|
+
* GitHub repository owner/organization.
|
|
14
|
+
*/
|
|
15
|
+
owner: string;
|
|
16
|
+
/**
|
|
17
|
+
* GitHub repository name.
|
|
18
|
+
*/
|
|
19
|
+
repo: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get latest release tag matching a tool prefix.
|
|
23
|
+
*
|
|
24
|
+
* Searches recent releases for the first tag matching the given prefix.
|
|
25
|
+
* Useful for finding latest versions of tools with date-based tags like
|
|
26
|
+
* `node-smol-20260105-c47753c` or `binject-20260106-1df5745`.
|
|
27
|
+
*
|
|
28
|
+
* @param toolPrefix - Tool name prefix to search for (e.g., 'node-smol-', 'binject-')
|
|
29
|
+
* @param repoConfig - Repository configuration (owner/repo)
|
|
30
|
+
* @param options - Additional options
|
|
31
|
+
* @returns Latest release tag or null if not found
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const tag = await getLatestRelease('node-smol-', {
|
|
36
|
+
* owner: 'SocketDev',
|
|
37
|
+
* repo: 'socket-btm'
|
|
38
|
+
* })
|
|
39
|
+
* // Returns: 'node-smol-20260105-c47753c'
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function getLatestRelease(toolPrefix: string, repoConfig: RepoConfig, options?: {
|
|
43
|
+
quiet?: boolean;
|
|
44
|
+
}): Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Get download URL for a specific release asset.
|
|
47
|
+
*
|
|
48
|
+
* @param tag - Release tag name (e.g., 'node-smol-20260105-c47753c')
|
|
49
|
+
* @param assetName - Asset name to download (e.g., 'node-linux-x64-musl')
|
|
50
|
+
* @param repoConfig - Repository configuration (owner/repo)
|
|
51
|
+
* @param options - Additional options
|
|
52
|
+
* @returns Browser download URL for the asset
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const url = await getReleaseAssetUrl(
|
|
57
|
+
* 'node-smol-20260105-c47753c',
|
|
58
|
+
* 'node-linux-x64-musl',
|
|
59
|
+
* { owner: 'SocketDev', repo: 'socket-btm' }
|
|
60
|
+
* )
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function getReleaseAssetUrl(tag: string, assetName: string, repoConfig: RepoConfig, options?: {
|
|
64
|
+
quiet?: boolean;
|
|
65
|
+
}): Promise<string | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Download a specific release asset.
|
|
68
|
+
*
|
|
69
|
+
* Uses browser_download_url to avoid consuming GitHub API quota.
|
|
70
|
+
* Automatically follows redirects and retries on failure.
|
|
71
|
+
*
|
|
72
|
+
* @param tag - Release tag name
|
|
73
|
+
* @param assetName - Asset name to download
|
|
74
|
+
* @param outputPath - Path to write the downloaded file
|
|
75
|
+
* @param repoConfig - Repository configuration (owner/repo)
|
|
76
|
+
* @param options - Additional options
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* await downloadReleaseAsset(
|
|
81
|
+
* 'node-smol-20260105-c47753c',
|
|
82
|
+
* 'node-linux-x64-musl',
|
|
83
|
+
* '/path/to/output/node',
|
|
84
|
+
* { owner: 'SocketDev', repo: 'socket-btm' }
|
|
85
|
+
* )
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function downloadReleaseAsset(tag: string, assetName: string, outputPath: string, repoConfig: RepoConfig, options?: {
|
|
89
|
+
quiet?: boolean;
|
|
90
|
+
}): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Configuration for downloading a GitHub release.
|
|
93
|
+
*/
|
|
94
|
+
export interface DownloadGitHubReleaseConfig {
|
|
95
|
+
/**
|
|
96
|
+
* GitHub repository owner/organization.
|
|
97
|
+
*/
|
|
98
|
+
owner: string;
|
|
99
|
+
/**
|
|
100
|
+
* GitHub repository name.
|
|
101
|
+
*/
|
|
102
|
+
repo: string;
|
|
103
|
+
/**
|
|
104
|
+
* Working directory (defaults to process.cwd()).
|
|
105
|
+
* Used to resolve relative paths in downloadDir.
|
|
106
|
+
*/
|
|
107
|
+
cwd?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Download destination directory.
|
|
110
|
+
* Can be absolute or relative to cwd.
|
|
111
|
+
* @default 'build/downloaded' (relative to cwd)
|
|
112
|
+
*/
|
|
113
|
+
downloadDir?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Tool name for directory structure (e.g., 'node-smol', 'binject', 'lief').
|
|
116
|
+
* Creates subdirectory: {downloadDir}/{toolName}/{platformArch}/
|
|
117
|
+
*/
|
|
118
|
+
toolName: string;
|
|
119
|
+
/**
|
|
120
|
+
* Platform-arch identifier (e.g., 'linux-x64-musl', 'darwin-arm64').
|
|
121
|
+
* Used for the download directory path.
|
|
122
|
+
*/
|
|
123
|
+
platformArch: string;
|
|
124
|
+
/**
|
|
125
|
+
* Binary filename (e.g., 'node', 'binject', 'lief', 'node.exe').
|
|
126
|
+
*/
|
|
127
|
+
binaryName: string;
|
|
128
|
+
/**
|
|
129
|
+
* Asset name on GitHub (e.g., 'node-linux-x64-musl', 'binject-darwin-arm64').
|
|
130
|
+
*/
|
|
131
|
+
assetName: string;
|
|
132
|
+
/**
|
|
133
|
+
* Tool prefix for finding latest release (e.g., 'node-smol-', 'binject-').
|
|
134
|
+
* Either this or `tag` must be provided.
|
|
135
|
+
*/
|
|
136
|
+
toolPrefix?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Specific release tag to download (e.g., 'node-smol-20260105-c47753c').
|
|
139
|
+
* If not provided, uses `toolPrefix` to find the latest release.
|
|
140
|
+
*/
|
|
141
|
+
tag?: string;
|
|
142
|
+
/**
|
|
143
|
+
* Suppress log messages.
|
|
144
|
+
* @default false
|
|
145
|
+
*/
|
|
146
|
+
quiet?: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Remove macOS quarantine attribute after download.
|
|
149
|
+
* Only applies when downloading on macOS for macOS binaries.
|
|
150
|
+
* @default true
|
|
151
|
+
*/
|
|
152
|
+
removeMacOSQuarantine?: boolean;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Download a binary from any GitHub repository with version caching.
|
|
156
|
+
*
|
|
157
|
+
* Downloads to: `{downloadDir}/{toolName}/{platformArch}/{binaryName}`
|
|
158
|
+
* Caches version in: `{downloadDir}/{toolName}/{platformArch}/.version`
|
|
159
|
+
*
|
|
160
|
+
* @param config - Download configuration
|
|
161
|
+
* @returns Path to the downloaded binary
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* // Download from any GitHub repo
|
|
166
|
+
* const nodePath = await downloadGitHubRelease({
|
|
167
|
+
* owner: 'nodejs',
|
|
168
|
+
* repo: 'node',
|
|
169
|
+
* cwd: process.cwd(),
|
|
170
|
+
* downloadDir: 'build/downloaded', // relative to cwd
|
|
171
|
+
* toolName: 'node',
|
|
172
|
+
* platformArch: 'linux-x64',
|
|
173
|
+
* binaryName: 'node',
|
|
174
|
+
* assetName: 'node-v20.10.0-linux-x64.tar.gz',
|
|
175
|
+
* tag: 'v20.10.0'
|
|
176
|
+
* })
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export declare function downloadGitHubRelease(config: DownloadGitHubReleaseConfig): Promise<string>;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Socket Lib - Built with esbuild */
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var github_exports = {};
|
|
31
|
+
__export(github_exports, {
|
|
32
|
+
SOCKET_BTM_REPO: () => SOCKET_BTM_REPO,
|
|
33
|
+
downloadGitHubRelease: () => downloadGitHubRelease,
|
|
34
|
+
downloadReleaseAsset: () => downloadReleaseAsset,
|
|
35
|
+
getLatestRelease: () => getLatestRelease,
|
|
36
|
+
getReleaseAssetUrl: () => getReleaseAssetUrl
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(github_exports);
|
|
39
|
+
var import_node_fs = require("node:fs");
|
|
40
|
+
var import_promises = require("node:fs/promises");
|
|
41
|
+
var import_node_path = __toESM(require("node:path"));
|
|
42
|
+
var import_fs = require("../fs.js");
|
|
43
|
+
var import_http_request = require("../http-request.js");
|
|
44
|
+
var import_logger = require("../logger.js");
|
|
45
|
+
var import_promises2 = require("../promises.js");
|
|
46
|
+
var import_spawn = require("../spawn.js");
|
|
47
|
+
const logger = (0, import_logger.getDefaultLogger)();
|
|
48
|
+
const SOCKET_BTM_REPO = {
|
|
49
|
+
owner: "SocketDev",
|
|
50
|
+
repo: "socket-btm"
|
|
51
|
+
};
|
|
52
|
+
const RETRY_CONFIG = Object.freeze({
|
|
53
|
+
__proto__: null,
|
|
54
|
+
// Exponential backoff: delay doubles with each retry (5s, 10s, 20s).
|
|
55
|
+
backoffFactor: 2,
|
|
56
|
+
// Initial delay before first retry.
|
|
57
|
+
baseDelayMs: 5e3,
|
|
58
|
+
// Maximum number of retry attempts (excluding initial request).
|
|
59
|
+
retries: 2
|
|
60
|
+
});
|
|
61
|
+
function getAuthHeaders() {
|
|
62
|
+
const token = process.env["GH_TOKEN"] || process.env["GITHUB_TOKEN"];
|
|
63
|
+
const headers = {
|
|
64
|
+
Accept: "application/vnd.github+json",
|
|
65
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
66
|
+
};
|
|
67
|
+
if (token) {
|
|
68
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
69
|
+
}
|
|
70
|
+
return headers;
|
|
71
|
+
}
|
|
72
|
+
async function getLatestRelease(toolPrefix, repoConfig, options = {}) {
|
|
73
|
+
const { owner, repo } = repoConfig;
|
|
74
|
+
const { quiet = false } = options;
|
|
75
|
+
return await (0, import_promises2.pRetry)(
|
|
76
|
+
async () => {
|
|
77
|
+
const response = await (0, import_http_request.httpRequest)(
|
|
78
|
+
`https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`,
|
|
79
|
+
{
|
|
80
|
+
headers: getAuthHeaders()
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`Failed to fetch releases: ${response.status}`);
|
|
85
|
+
}
|
|
86
|
+
const releases = JSON.parse(response.body.toString("utf8"));
|
|
87
|
+
for (const release of releases) {
|
|
88
|
+
const { tag_name: tag } = release;
|
|
89
|
+
if (tag.startsWith(toolPrefix)) {
|
|
90
|
+
if (!quiet) {
|
|
91
|
+
logger.info(`Found release: ${tag}`);
|
|
92
|
+
}
|
|
93
|
+
return tag;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!quiet) {
|
|
97
|
+
logger.info(`No ${toolPrefix} release found in latest 100 releases`);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
...RETRY_CONFIG,
|
|
103
|
+
onRetry: (attempt, error) => {
|
|
104
|
+
if (!quiet) {
|
|
105
|
+
logger.info(
|
|
106
|
+
`Retry attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} for ${toolPrefix} release...`
|
|
107
|
+
);
|
|
108
|
+
logger.warn(
|
|
109
|
+
`Attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
async function getReleaseAssetUrl(tag, assetName, repoConfig, options = {}) {
|
|
118
|
+
const { owner, repo } = repoConfig;
|
|
119
|
+
const { quiet = false } = options;
|
|
120
|
+
return await (0, import_promises2.pRetry)(
|
|
121
|
+
async () => {
|
|
122
|
+
const response = await (0, import_http_request.httpRequest)(
|
|
123
|
+
`https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`,
|
|
124
|
+
{
|
|
125
|
+
headers: getAuthHeaders()
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new Error(`Failed to fetch release ${tag}: ${response.status}`);
|
|
130
|
+
}
|
|
131
|
+
const release = JSON.parse(response.body.toString("utf8"));
|
|
132
|
+
const asset = release.assets.find(
|
|
133
|
+
(a) => a.name === assetName
|
|
134
|
+
);
|
|
135
|
+
if (!asset) {
|
|
136
|
+
throw new Error(`Asset ${assetName} not found in release ${tag}`);
|
|
137
|
+
}
|
|
138
|
+
if (!quiet) {
|
|
139
|
+
logger.info(`Found asset: ${assetName}`);
|
|
140
|
+
}
|
|
141
|
+
return asset.browser_download_url;
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
...RETRY_CONFIG,
|
|
145
|
+
onRetry: (attempt, error) => {
|
|
146
|
+
if (!quiet) {
|
|
147
|
+
logger.info(
|
|
148
|
+
`Retry attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} for asset URL...`
|
|
149
|
+
);
|
|
150
|
+
logger.warn(
|
|
151
|
+
`Attempt ${attempt + 1}/${RETRY_CONFIG.retries + 1} failed: ${error instanceof Error ? error.message : String(error)}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
async function downloadReleaseAsset(tag, assetName, outputPath, repoConfig, options = {}) {
|
|
160
|
+
const { owner, repo } = repoConfig;
|
|
161
|
+
const { quiet = false } = options;
|
|
162
|
+
const downloadUrl = await getReleaseAssetUrl(
|
|
163
|
+
tag,
|
|
164
|
+
assetName,
|
|
165
|
+
{ owner, repo },
|
|
166
|
+
{ quiet }
|
|
167
|
+
);
|
|
168
|
+
if (!downloadUrl) {
|
|
169
|
+
throw new Error(`Asset ${assetName} not found in release ${tag}`);
|
|
170
|
+
}
|
|
171
|
+
await (0, import_fs.safeMkdir)(import_node_path.default.dirname(outputPath));
|
|
172
|
+
await (0, import_http_request.httpDownload)(downloadUrl, outputPath, {
|
|
173
|
+
logger: quiet ? void 0 : logger,
|
|
174
|
+
progressInterval: 10,
|
|
175
|
+
retries: 2,
|
|
176
|
+
retryDelay: 5e3
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async function downloadGitHubRelease(config) {
|
|
180
|
+
const {
|
|
181
|
+
assetName,
|
|
182
|
+
binaryName,
|
|
183
|
+
cwd = process.cwd(),
|
|
184
|
+
downloadDir = "build/downloaded",
|
|
185
|
+
owner,
|
|
186
|
+
platformArch,
|
|
187
|
+
quiet = false,
|
|
188
|
+
removeMacOSQuarantine = true,
|
|
189
|
+
repo,
|
|
190
|
+
tag: explicitTag,
|
|
191
|
+
toolName,
|
|
192
|
+
toolPrefix
|
|
193
|
+
} = config;
|
|
194
|
+
let tag;
|
|
195
|
+
if (explicitTag) {
|
|
196
|
+
tag = explicitTag;
|
|
197
|
+
} else if (toolPrefix) {
|
|
198
|
+
const latestTag = await getLatestRelease(
|
|
199
|
+
toolPrefix,
|
|
200
|
+
{ owner, repo },
|
|
201
|
+
{ quiet }
|
|
202
|
+
);
|
|
203
|
+
if (!latestTag) {
|
|
204
|
+
throw new Error(`No ${toolPrefix} release found in ${owner}/${repo}`);
|
|
205
|
+
}
|
|
206
|
+
tag = latestTag;
|
|
207
|
+
} else {
|
|
208
|
+
throw new Error("Either toolPrefix or tag must be provided");
|
|
209
|
+
}
|
|
210
|
+
const resolvedDownloadDir = import_node_path.default.isAbsolute(downloadDir) ? downloadDir : import_node_path.default.join(cwd, downloadDir);
|
|
211
|
+
const binaryDir = import_node_path.default.join(resolvedDownloadDir, toolName, platformArch);
|
|
212
|
+
const binaryPath = import_node_path.default.join(binaryDir, binaryName);
|
|
213
|
+
const versionPath = import_node_path.default.join(binaryDir, ".version");
|
|
214
|
+
if ((0, import_node_fs.existsSync)(versionPath) && (0, import_node_fs.existsSync)(binaryPath)) {
|
|
215
|
+
const cachedVersion = (await (0, import_promises.readFile)(versionPath, "utf8")).trim();
|
|
216
|
+
if (cachedVersion === tag) {
|
|
217
|
+
if (!quiet) {
|
|
218
|
+
logger.info(`Using cached ${toolName} (${platformArch}): ${binaryPath}`);
|
|
219
|
+
}
|
|
220
|
+
return binaryPath;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!quiet) {
|
|
224
|
+
logger.info(`Downloading ${toolName} for ${platformArch}...`);
|
|
225
|
+
}
|
|
226
|
+
await downloadReleaseAsset(
|
|
227
|
+
tag,
|
|
228
|
+
assetName,
|
|
229
|
+
binaryPath,
|
|
230
|
+
{ owner, repo },
|
|
231
|
+
{ quiet }
|
|
232
|
+
);
|
|
233
|
+
const isWindows = binaryName.endsWith(".exe");
|
|
234
|
+
if (!isWindows) {
|
|
235
|
+
await (0, import_promises.chmod)(binaryPath, 493);
|
|
236
|
+
if (removeMacOSQuarantine && process.platform === "darwin" && platformArch.startsWith("darwin")) {
|
|
237
|
+
try {
|
|
238
|
+
await (0, import_spawn.spawn)("xattr", ["-d", "com.apple.quarantine", binaryPath], {
|
|
239
|
+
stdio: "ignore"
|
|
240
|
+
});
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
await (0, import_promises.writeFile)(versionPath, tag, "utf8");
|
|
246
|
+
if (!quiet) {
|
|
247
|
+
logger.info(`Downloaded ${toolName} to ${binaryPath}`);
|
|
248
|
+
}
|
|
249
|
+
return binaryPath;
|
|
250
|
+
}
|
|
251
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
252
|
+
0 && (module.exports = {
|
|
253
|
+
SOCKET_BTM_REPO,
|
|
254
|
+
downloadGitHubRelease,
|
|
255
|
+
downloadReleaseAsset,
|
|
256
|
+
getLatestRelease,
|
|
257
|
+
getReleaseAssetUrl
|
|
258
|
+
});
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform type for socket-btm binaries.
|
|
3
|
+
*/
|
|
4
|
+
export type Platform = 'darwin' | 'linux' | 'win32';
|
|
5
|
+
/**
|
|
6
|
+
* Architecture type for socket-btm binaries.
|
|
7
|
+
*/
|
|
8
|
+
export type Arch = 'arm64' | 'x64';
|
|
9
|
+
/**
|
|
10
|
+
* Linux libc variant.
|
|
11
|
+
*/
|
|
12
|
+
export type Libc = 'musl' | 'glibc';
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for downloading socket-btm binary releases.
|
|
15
|
+
*/
|
|
16
|
+
export interface SocketBtmBinaryConfig {
|
|
17
|
+
/**
|
|
18
|
+
* Working directory (defaults to process.cwd()).
|
|
19
|
+
*/
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Download destination directory.
|
|
23
|
+
* Can be absolute or relative to cwd.
|
|
24
|
+
* @default 'build/downloaded' (relative to cwd)
|
|
25
|
+
*
|
|
26
|
+
* Inspired by: gh release download --dir
|
|
27
|
+
*/
|
|
28
|
+
downloadDir?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Tool/package name for directory structure and release matching.
|
|
31
|
+
* Similar to: brew install <formula>, cargo install <crate>
|
|
32
|
+
*
|
|
33
|
+
* Examples: 'node-smol', 'binject', 'binflate'
|
|
34
|
+
*
|
|
35
|
+
* Used for:
|
|
36
|
+
* - Directory path: {downloadDir}/{tool}/{platformArch}/
|
|
37
|
+
* - Finding release: Searches for tags starting with '{tool}-'
|
|
38
|
+
*/
|
|
39
|
+
tool: string;
|
|
40
|
+
/**
|
|
41
|
+
* Binary/executable name (without extension).
|
|
42
|
+
* Similar to: brew formula→binary mapping (postgresql→psql, imagemagick→magick)
|
|
43
|
+
*
|
|
44
|
+
* Examples: 'node', 'binject', 'psql', 'magick'
|
|
45
|
+
*
|
|
46
|
+
* Used to construct:
|
|
47
|
+
* - Asset pattern: {bin}-{platform}-{arch}[-musl][.exe]
|
|
48
|
+
* - Output filename: {bin} or {bin}.exe
|
|
49
|
+
*
|
|
50
|
+
* Presence of this field indicates binary download (vs asset download).
|
|
51
|
+
*
|
|
52
|
+
* @default tool (e.g., 'binject'→'binject', but 'node-smol'→'node-smol')
|
|
53
|
+
*/
|
|
54
|
+
bin?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Target platform (defaults to current platform).
|
|
57
|
+
*/
|
|
58
|
+
targetPlatform?: Platform;
|
|
59
|
+
/**
|
|
60
|
+
* Target architecture (defaults to current arch).
|
|
61
|
+
*/
|
|
62
|
+
targetArch?: Arch;
|
|
63
|
+
/**
|
|
64
|
+
* Linux libc variant (musl or glibc).
|
|
65
|
+
* Defaults to musl for Linux for broader compatibility.
|
|
66
|
+
* Ignored for non-Linux platforms.
|
|
67
|
+
*/
|
|
68
|
+
libc?: Libc;
|
|
69
|
+
/**
|
|
70
|
+
* Specific release tag to download.
|
|
71
|
+
* Inspired by: gh release download <tag>
|
|
72
|
+
*
|
|
73
|
+
* If not provided, downloads the latest release matching '{tool}-*' pattern.
|
|
74
|
+
*
|
|
75
|
+
* Examples: 'node-smol-20260105-c47753c', 'binject-20260106-1df5745'
|
|
76
|
+
*/
|
|
77
|
+
tag?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Suppress log messages.
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
82
|
+
quiet?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Remove macOS quarantine attribute after download.
|
|
85
|
+
* Only applies when downloading on macOS for macOS binaries.
|
|
86
|
+
* @default true
|
|
87
|
+
*/
|
|
88
|
+
removeMacOSQuarantine?: boolean;
|
|
89
|
+
// Discriminator: presence of 'asset' means this is NOT a binary config
|
|
90
|
+
asset?: never;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Configuration for downloading socket-btm generic assets.
|
|
94
|
+
*/
|
|
95
|
+
export interface SocketBtmAssetConfig {
|
|
96
|
+
/**
|
|
97
|
+
* Working directory (defaults to process.cwd()).
|
|
98
|
+
*/
|
|
99
|
+
cwd?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Download destination directory.
|
|
102
|
+
* Can be absolute or relative to cwd.
|
|
103
|
+
* @default 'build/downloaded' (relative to cwd)
|
|
104
|
+
*
|
|
105
|
+
* Inspired by: gh release download --dir
|
|
106
|
+
*/
|
|
107
|
+
downloadDir?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Tool/package name for directory structure and release matching.
|
|
110
|
+
*
|
|
111
|
+
* Examples: 'yoga-layout', 'onnxruntime', 'models'
|
|
112
|
+
*
|
|
113
|
+
* Used for:
|
|
114
|
+
* - Directory path: {downloadDir}/{tool}/assets/
|
|
115
|
+
* - Finding release: Searches for tags starting with '{tool}-'
|
|
116
|
+
*/
|
|
117
|
+
tool: string;
|
|
118
|
+
/**
|
|
119
|
+
* Asset name pattern on GitHub.
|
|
120
|
+
* Inspired by: gh release download --pattern
|
|
121
|
+
*
|
|
122
|
+
* Examples: 'yoga-sync.mjs', 'ort-wasm-simd.wasm', '*.onnx'
|
|
123
|
+
*
|
|
124
|
+
* Presence of this field indicates asset download (vs binary download).
|
|
125
|
+
*/
|
|
126
|
+
asset: string;
|
|
127
|
+
/**
|
|
128
|
+
* Output filename (e.g., 'yoga-sync.mjs').
|
|
129
|
+
* Inspired by: gh release download --output
|
|
130
|
+
*
|
|
131
|
+
* @default asset (uses the asset name as-is)
|
|
132
|
+
*/
|
|
133
|
+
output?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Specific release tag to download.
|
|
136
|
+
* Inspired by: gh release download <tag>
|
|
137
|
+
*
|
|
138
|
+
* If not provided, downloads the latest release matching '{tool}-*' pattern.
|
|
139
|
+
*
|
|
140
|
+
* Examples: 'yoga-layout-v20260106-a39285c', 'onnxruntime-v20260106-a39285c'
|
|
141
|
+
*/
|
|
142
|
+
tag?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Suppress log messages.
|
|
145
|
+
* @default false
|
|
146
|
+
*/
|
|
147
|
+
quiet?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Remove macOS quarantine attribute after download.
|
|
150
|
+
* @default false (not needed for non-executable assets)
|
|
151
|
+
*/
|
|
152
|
+
removeMacOSQuarantine?: boolean;
|
|
153
|
+
// Discriminators: mutually exclusive with binary-specific fields
|
|
154
|
+
bin?: never;
|
|
155
|
+
targetPlatform?: never;
|
|
156
|
+
targetArch?: never;
|
|
157
|
+
libc?: never;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Configuration for downloading socket-btm releases (binary or asset).
|
|
161
|
+
*/
|
|
162
|
+
export type SocketBtmReleaseConfig = SocketBtmBinaryConfig | SocketBtmAssetConfig;
|
|
163
|
+
/**
|
|
164
|
+
* Download a release from socket-btm.
|
|
165
|
+
*
|
|
166
|
+
* Generic function for downloading any socket-btm binary or asset.
|
|
167
|
+
* Handles both platform-specific binaries and generic assets.
|
|
168
|
+
*
|
|
169
|
+
* @param config - Download configuration
|
|
170
|
+
* @returns Path to the downloaded file
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* // Binary: node-smol (like: brew install nodejs → node)
|
|
175
|
+
* const nodePath = await downloadSocketBtmRelease({
|
|
176
|
+
* tool: 'node-smol',
|
|
177
|
+
* bin: 'node'
|
|
178
|
+
* })
|
|
179
|
+
*
|
|
180
|
+
* // Binary: binject (like: cargo install binject → binject)
|
|
181
|
+
* const binjectPath = await downloadSocketBtmRelease({
|
|
182
|
+
* tool: 'binject'
|
|
183
|
+
* })
|
|
184
|
+
*
|
|
185
|
+
* // Binary: cross-platform
|
|
186
|
+
* const binflatePath = await downloadSocketBtmRelease({
|
|
187
|
+
* tool: 'binflate',
|
|
188
|
+
* targetPlatform: 'linux',
|
|
189
|
+
* targetArch: 'x64',
|
|
190
|
+
* libc: 'musl'
|
|
191
|
+
* })
|
|
192
|
+
*
|
|
193
|
+
* // Asset: WASM file
|
|
194
|
+
* const yogaPath = await downloadSocketBtmRelease({
|
|
195
|
+
* tool: 'yoga-layout',
|
|
196
|
+
* asset: 'yoga-sync.mjs'
|
|
197
|
+
* })
|
|
198
|
+
*
|
|
199
|
+
* // Asset: with custom output name
|
|
200
|
+
* const ortPath = await downloadSocketBtmRelease({
|
|
201
|
+
* tool: 'onnxruntime',
|
|
202
|
+
* asset: 'ort-wasm-simd.wasm',
|
|
203
|
+
* output: 'ort.wasm'
|
|
204
|
+
* })
|
|
205
|
+
*
|
|
206
|
+
* // Custom paths (like: gh release download --dir)
|
|
207
|
+
* await downloadSocketBtmRelease({
|
|
208
|
+
* tool: 'node-smol',
|
|
209
|
+
* bin: 'node',
|
|
210
|
+
* cwd: '/path/to/project',
|
|
211
|
+
* downloadDir: 'build/cache'
|
|
212
|
+
* })
|
|
213
|
+
*
|
|
214
|
+
* // Specific version (like: gh release download <tag>)
|
|
215
|
+
* await downloadSocketBtmRelease({
|
|
216
|
+
* tool: 'binject',
|
|
217
|
+
* tag: 'binject-20260106-1df5745'
|
|
218
|
+
* })
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export declare function downloadSocketBtmRelease(config: SocketBtmReleaseConfig): Promise<string>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Socket Lib - Built with esbuild */
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var socket_btm_exports = {};
|
|
31
|
+
__export(socket_btm_exports, {
|
|
32
|
+
downloadSocketBtmRelease: () => downloadSocketBtmRelease
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(socket_btm_exports);
|
|
35
|
+
var import_node_os = __toESM(require("node:os"));
|
|
36
|
+
var import_github = require("./github.js");
|
|
37
|
+
const ARCH_MAP = {
|
|
38
|
+
arm64: "arm64",
|
|
39
|
+
x64: "x64"
|
|
40
|
+
};
|
|
41
|
+
function getBinaryAssetName(binaryBaseName, platform, arch, libc) {
|
|
42
|
+
const mappedArch = ARCH_MAP[arch];
|
|
43
|
+
if (!mappedArch) {
|
|
44
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
45
|
+
}
|
|
46
|
+
const muslSuffix = platform === "linux" && libc === "musl" ? "-musl" : "";
|
|
47
|
+
const ext = platform === "win32" ? ".exe" : "";
|
|
48
|
+
if (platform === "darwin") {
|
|
49
|
+
return `${binaryBaseName}-darwin-${mappedArch}${ext}`;
|
|
50
|
+
}
|
|
51
|
+
if (platform === "linux") {
|
|
52
|
+
return `${binaryBaseName}-linux-${mappedArch}${muslSuffix}${ext}`;
|
|
53
|
+
}
|
|
54
|
+
if (platform === "win32") {
|
|
55
|
+
return `${binaryBaseName}-win-${mappedArch}${ext}`;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
58
|
+
}
|
|
59
|
+
function getPlatformArch(platform, arch, libc) {
|
|
60
|
+
const mappedArch = ARCH_MAP[arch];
|
|
61
|
+
if (!mappedArch) {
|
|
62
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
63
|
+
}
|
|
64
|
+
const muslSuffix = platform === "linux" && libc === "musl" ? "-musl" : "";
|
|
65
|
+
return `${platform}-${mappedArch}${muslSuffix}`;
|
|
66
|
+
}
|
|
67
|
+
function getBinaryName(binaryBaseName, platform) {
|
|
68
|
+
return platform === "win32" ? `${binaryBaseName}.exe` : binaryBaseName;
|
|
69
|
+
}
|
|
70
|
+
async function downloadSocketBtmRelease(config) {
|
|
71
|
+
const { cwd, downloadDir, quiet = false, tag, tool } = config;
|
|
72
|
+
const toolPrefix = `${tool}-`;
|
|
73
|
+
let downloadConfig;
|
|
74
|
+
if ("asset" in config) {
|
|
75
|
+
const {
|
|
76
|
+
asset,
|
|
77
|
+
output,
|
|
78
|
+
removeMacOSQuarantine = false
|
|
79
|
+
} = config;
|
|
80
|
+
const outputName = output || asset;
|
|
81
|
+
const platformArch = "assets";
|
|
82
|
+
downloadConfig = {
|
|
83
|
+
owner: import_github.SOCKET_BTM_REPO.owner,
|
|
84
|
+
repo: import_github.SOCKET_BTM_REPO.repo,
|
|
85
|
+
cwd,
|
|
86
|
+
downloadDir,
|
|
87
|
+
toolName: tool,
|
|
88
|
+
platformArch,
|
|
89
|
+
binaryName: outputName,
|
|
90
|
+
assetName: asset,
|
|
91
|
+
toolPrefix,
|
|
92
|
+
tag,
|
|
93
|
+
quiet,
|
|
94
|
+
removeMacOSQuarantine
|
|
95
|
+
};
|
|
96
|
+
} else {
|
|
97
|
+
const {
|
|
98
|
+
bin,
|
|
99
|
+
libc,
|
|
100
|
+
removeMacOSQuarantine = true,
|
|
101
|
+
targetArch,
|
|
102
|
+
targetPlatform
|
|
103
|
+
} = config;
|
|
104
|
+
const baseName = bin || tool;
|
|
105
|
+
const platform = targetPlatform || import_node_os.default.platform();
|
|
106
|
+
const arch = targetArch || import_node_os.default.arch();
|
|
107
|
+
const libcType = libc || (platform === "linux" ? "musl" : void 0);
|
|
108
|
+
const assetName = getBinaryAssetName(baseName, platform, arch, libcType);
|
|
109
|
+
const platformArch = getPlatformArch(platform, arch, libcType);
|
|
110
|
+
const binaryName = getBinaryName(baseName, platform);
|
|
111
|
+
downloadConfig = {
|
|
112
|
+
owner: import_github.SOCKET_BTM_REPO.owner,
|
|
113
|
+
repo: import_github.SOCKET_BTM_REPO.repo,
|
|
114
|
+
cwd,
|
|
115
|
+
downloadDir,
|
|
116
|
+
toolName: tool,
|
|
117
|
+
platformArch,
|
|
118
|
+
binaryName,
|
|
119
|
+
assetName,
|
|
120
|
+
toolPrefix,
|
|
121
|
+
tag,
|
|
122
|
+
quiet,
|
|
123
|
+
removeMacOSQuarantine
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return await (0, import_github.downloadGitHubRelease)(downloadConfig);
|
|
127
|
+
}
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
downloadSocketBtmRelease
|
|
131
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socketsecurity/lib",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"packageManager": "pnpm@10.26.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Core utilities and infrastructure for Socket.dev security tools",
|
|
@@ -523,6 +523,14 @@
|
|
|
523
523
|
"types": "./dist/regexps.d.ts",
|
|
524
524
|
"default": "./dist/regexps.js"
|
|
525
525
|
},
|
|
526
|
+
"./releases/github": {
|
|
527
|
+
"types": "./dist/releases/github.d.ts",
|
|
528
|
+
"default": "./dist/releases/github.js"
|
|
529
|
+
},
|
|
530
|
+
"./releases/socket-btm": {
|
|
531
|
+
"types": "./dist/releases/socket-btm.d.ts",
|
|
532
|
+
"default": "./dist/releases/socket-btm.js"
|
|
533
|
+
},
|
|
526
534
|
"./sea": {
|
|
527
535
|
"types": "./dist/sea.d.ts",
|
|
528
536
|
"default": "./dist/sea.js"
|