@socketsecurity/lib 5.3.0 → 5.4.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cover/code.js +12 -4
  3. package/dist/dlx/cache.js +10 -2
  4. package/dist/dlx/manifest.js +45 -41
  5. package/dist/env/rewire.js +10 -2
  6. package/dist/external/@inquirer/checkbox.js +4 -2528
  7. package/dist/external/@inquirer/confirm.js +4 -2371
  8. package/dist/external/@inquirer/input.js +4 -2395
  9. package/dist/external/@inquirer/password.js +4 -2503
  10. package/dist/external/@inquirer/search.js +4 -2500
  11. package/dist/external/@inquirer/select.js +4 -2617
  12. package/dist/external/del.js +4 -7139
  13. package/dist/external/fast-glob.js +4 -5776
  14. package/dist/external/inquirer-pack.js +4610 -0
  15. package/dist/external/npm-core.js +3 -1
  16. package/dist/external/pico-pack.js +7162 -0
  17. package/dist/external/picomatch.js +4 -1523
  18. package/dist/external/spdx-correct.js +4 -1384
  19. package/dist/external/spdx-expression-parse.js +4 -1047
  20. package/dist/external/spdx-pack.js +1640 -0
  21. package/dist/external/validate-npm-package-name.js +4 -104
  22. package/dist/http-request.js +10 -2
  23. package/dist/ipc.js +53 -29
  24. package/dist/packages/isolation.js +45 -23
  25. package/dist/packages/licenses.js +10 -2
  26. package/dist/paths/socket.d.ts +2 -2
  27. package/dist/paths/socket.js +27 -21
  28. package/dist/process-lock.js +23 -14
  29. package/dist/releases/github.d.ts +67 -41
  30. package/dist/releases/github.js +142 -100
  31. package/dist/releases/socket-btm.d.ts +40 -33
  32. package/dist/releases/socket-btm.js +45 -5
  33. package/dist/spawn.js +10 -3
  34. package/dist/stdio/mask.d.ts +6 -21
  35. package/dist/stdio/mask.js +18 -14
  36. package/dist/themes/context.js +10 -2
  37. package/package.json +2 -1
@@ -22,11 +22,20 @@ __export(process_lock_exports, {
22
22
  processLock: () => processLock
23
23
  });
24
24
  module.exports = __toCommonJS(process_lock_exports);
25
- var import_fs = require("fs");
26
- var import_fs2 = require("./fs");
25
+ var import_fs = require("./fs");
27
26
  var import_logger = require("./logger");
28
27
  var import_promises = require("./promises");
29
28
  var import_signal_exit = require("./signal-exit");
29
+ let _fs;
30
+ // @__NO_SIDE_EFFECTS__
31
+ function getFs() {
32
+ if (_fs === void 0) {
33
+ _fs = require("fs");
34
+ }
35
+ return _fs;
36
+ }
37
+ const fs = /* @__PURE__ */ getFs();
38
+ const { existsSync, mkdirSync, statSync, utimesSync } = fs;
30
39
  const logger = (0, import_logger.getDefaultLogger)();
31
40
  class ProcessLockManager {
32
41
  activeLocks = /* @__PURE__ */ new Set();
@@ -47,8 +56,8 @@ class ProcessLockManager {
47
56
  this.touchTimers.clear();
48
57
  for (const lockPath of this.activeLocks) {
49
58
  try {
50
- if ((0, import_fs.existsSync)(lockPath)) {
51
- (0, import_fs2.safeDeleteSync)(lockPath, { recursive: true });
59
+ if (existsSync(lockPath)) {
60
+ (0, import_fs.safeDeleteSync)(lockPath, { recursive: true });
52
61
  }
53
62
  } catch {
54
63
  }
@@ -64,9 +73,9 @@ class ProcessLockManager {
64
73
  */
65
74
  touchLock(lockPath) {
66
75
  try {
67
- if ((0, import_fs.existsSync)(lockPath)) {
76
+ if (existsSync(lockPath)) {
68
77
  const now = /* @__PURE__ */ new Date();
69
- (0, import_fs.utimesSync)(lockPath, now, now);
78
+ utimesSync(lockPath, now, now);
70
79
  }
71
80
  } catch (error) {
72
81
  logger.warn(
@@ -114,10 +123,10 @@ class ProcessLockManager {
114
123
  */
115
124
  isStale(lockPath, staleMs) {
116
125
  try {
117
- if (!(0, import_fs.existsSync)(lockPath)) {
126
+ if (!existsSync(lockPath)) {
118
127
  return false;
119
128
  }
120
- const stats = (0, import_fs.statSync)(lockPath);
129
+ const stats = statSync(lockPath);
121
130
  const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1e3);
122
131
  const staleSeconds = Math.floor(staleMs / 1e3);
123
132
  return ageSeconds > staleSeconds;
@@ -160,17 +169,17 @@ class ProcessLockManager {
160
169
  return await (0, import_promises.pRetry)(
161
170
  async () => {
162
171
  try {
163
- if ((0, import_fs.existsSync)(lockPath) && this.isStale(lockPath, staleMs)) {
172
+ if (existsSync(lockPath) && this.isStale(lockPath, staleMs)) {
164
173
  logger.log(`Removing stale lock: ${lockPath}`);
165
174
  try {
166
- (0, import_fs2.safeDeleteSync)(lockPath, { recursive: true });
175
+ (0, import_fs.safeDeleteSync)(lockPath, { recursive: true });
167
176
  } catch {
168
177
  }
169
178
  }
170
- if ((0, import_fs.existsSync)(lockPath)) {
179
+ if (existsSync(lockPath)) {
171
180
  throw new Error(`Lock already exists: ${lockPath}`);
172
181
  }
173
- (0, import_fs.mkdirSync)(lockPath, { recursive: true });
182
+ mkdirSync(lockPath, { recursive: true });
174
183
  this.activeLocks.add(lockPath);
175
184
  this.startTouchTimer(lockPath, touchIntervalMs);
176
185
  return () => this.release(lockPath);
@@ -246,8 +255,8 @@ To resolve:
246
255
  release(lockPath) {
247
256
  this.stopTouchTimer(lockPath);
248
257
  try {
249
- if ((0, import_fs.existsSync)(lockPath)) {
250
- (0, import_fs2.safeDeleteSync)(lockPath, { recursive: true });
258
+ if (existsSync(lockPath)) {
259
+ (0, import_fs.safeDeleteSync)(lockPath, { recursive: true });
251
260
  }
252
261
  this.activeLocks.delete(lockPath);
253
262
  } catch (error) {
@@ -1,69 +1,84 @@
1
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.
2
+ * Pattern for matching release assets.
3
+ * Can be either:
4
+ * - A string with glob pattern syntax
5
+ * - A prefix/suffix pair for explicit matching (backward compatible)
6
+ * - A RegExp for complex patterns
7
+ *
8
+ * String patterns support full glob syntax via picomatch.
9
+ * Examples:
10
+ * - Simple wildcard: yoga-sync-*.mjs matches yoga-sync-abc123.mjs
11
+ * - Complex: models-*.tar.gz matches models-2024-01-15.tar.gz
12
+ * - Prefix wildcard: *-models.tar.gz matches foo-models.tar.gz
13
+ * - Suffix wildcard: yoga-* matches yoga-layout
14
+ * - Brace expansion: {yoga,models}-*.{mjs,js} matches yoga-abc.mjs or models-xyz.js
15
+ *
16
+ * For backward compatibility, prefix/suffix objects are still supported but glob patterns are recommended.
10
17
  */
11
- export interface RepoConfig {
12
- /**
13
- * GitHub repository owner/organization.
14
- */
15
- owner: string;
16
- /**
17
- * GitHub repository name.
18
- */
19
- repo: string;
20
- }
18
+ export type AssetPattern = string | {
19
+ prefix: string;
20
+ suffix: string;
21
+ } | RegExp;
21
22
  /**
22
23
  * Configuration for downloading a GitHub release.
23
24
  */
24
25
  export interface DownloadGitHubReleaseConfig {
25
- /** GitHub repository owner/organization. */
26
- owner: string;
27
- /** GitHub repository name. */
28
- repo: string;
26
+ /** Asset name on GitHub. */
27
+ assetName: string;
28
+ /** Binary filename (e.g., 'node', 'binject'). */
29
+ binaryName: string;
29
30
  /** Working directory (defaults to process.cwd()). */
30
31
  cwd?: string;
31
32
  /** Download destination directory. @default 'build/downloaded' */
32
33
  downloadDir?: string;
33
- /** Tool name for directory structure. */
34
- toolName: string;
34
+ /** GitHub repository owner/organization. */
35
+ owner: string;
35
36
  /** Platform-arch identifier (e.g., 'linux-x64-musl'). */
36
37
  platformArch: string;
37
- /** Binary filename (e.g., 'node', 'binject'). */
38
- binaryName: string;
39
- /** Asset name on GitHub. */
40
- assetName: string;
41
- /** Tool prefix for finding latest release. */
42
- toolPrefix?: string;
43
- /** Specific release tag to download. */
44
- tag?: string;
45
38
  /** Suppress log messages. @default false */
46
39
  quiet?: boolean;
47
40
  /** Remove macOS quarantine attribute after download. @default true */
48
41
  removeMacOSQuarantine?: boolean;
42
+ /** GitHub repository name. */
43
+ repo: string;
44
+ /** Specific release tag to download. */
45
+ tag?: string;
46
+ /** Tool name for directory structure. */
47
+ toolName: string;
48
+ /** Tool prefix for finding latest release. */
49
+ toolPrefix?: string;
49
50
  }
50
51
  /**
51
- * Download a binary from any GitHub repository with version caching.
52
- *
53
- * @param config - Download configuration
54
- * @returns Path to the downloaded binary
52
+ * Configuration for repository access.
55
53
  */
56
- export declare function downloadGitHubRelease(config: DownloadGitHubReleaseConfig): Promise<string>;
54
+ export interface RepoConfig {
55
+ /**
56
+ * GitHub repository owner/organization.
57
+ */
58
+ owner: string;
59
+ /**
60
+ * GitHub repository name.
61
+ */
62
+ repo: string;
63
+ }
64
+ /**
65
+ * Socket-btm GitHub repository configuration.
66
+ */
67
+ export declare const SOCKET_BTM_REPO: {
68
+ readonly owner: "SocketDev";
69
+ readonly repo: "socket-btm";
70
+ };
57
71
  /**
58
72
  * Download a specific release asset.
73
+ * Supports pattern matching for dynamic asset discovery.
59
74
  *
60
75
  * @param tag - Release tag name
61
- * @param assetName - Asset name to download
76
+ * @param assetPattern - Asset name or pattern (glob string, prefix/suffix object, or RegExp)
62
77
  * @param outputPath - Path to write the downloaded file
63
78
  * @param repoConfig - Repository configuration (owner/repo)
64
79
  * @param options - Additional options
65
80
  */
66
- export declare function downloadReleaseAsset(tag: string, assetName: string, outputPath: string, repoConfig: RepoConfig, options?: {
81
+ export declare function downloadReleaseAsset(tag: string, assetPattern: string | AssetPattern, outputPath: string, repoConfig: RepoConfig, options?: {
67
82
  quiet?: boolean;
68
83
  }): Promise<void>;
69
84
  /**
@@ -75,24 +90,35 @@ export declare function downloadReleaseAsset(tag: string, assetName: string, out
75
90
  export declare function getAuthHeaders(): Record<string, string>;
76
91
  /**
77
92
  * Get latest release tag matching a tool prefix.
93
+ * Optionally filter by releases containing a matching asset.
78
94
  *
79
95
  * @param toolPrefix - Tool name prefix to search for (e.g., 'node-smol-')
80
96
  * @param repoConfig - Repository configuration (owner/repo)
81
97
  * @param options - Additional options
98
+ * @param options.assetPattern - Optional pattern to filter releases by matching asset
82
99
  * @returns Latest release tag or null if not found
83
100
  */
84
101
  export declare function getLatestRelease(toolPrefix: string, repoConfig: RepoConfig, options?: {
102
+ assetPattern?: AssetPattern;
85
103
  quiet?: boolean;
86
104
  }): Promise<string | null>;
87
105
  /**
88
106
  * Get download URL for a specific release asset.
107
+ * Supports pattern matching for dynamic asset discovery.
89
108
  *
90
109
  * @param tag - Release tag name
91
- * @param assetName - Asset name to download
110
+ * @param assetPattern - Asset name or pattern (glob string, prefix/suffix object, or RegExp)
92
111
  * @param repoConfig - Repository configuration (owner/repo)
93
112
  * @param options - Additional options
94
113
  * @returns Browser download URL for the asset
95
114
  */
96
- export declare function getReleaseAssetUrl(tag: string, assetName: string, repoConfig: RepoConfig, options?: {
115
+ export declare function getReleaseAssetUrl(tag: string, assetPattern: string | AssetPattern, repoConfig: RepoConfig, options?: {
97
116
  quiet?: boolean;
98
117
  }): Promise<string | null>;
118
+ /**
119
+ * Download a binary from any GitHub repository with version caching.
120
+ *
121
+ * @param config - Download configuration
122
+ * @returns Path to the downloaded binary
123
+ */
124
+ export declare function downloadGitHubRelease(config: DownloadGitHubReleaseConfig): Promise<string>;
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  /* Socket Lib - Built with esbuild */
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
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
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
  var github_exports = {};
21
31
  __export(github_exports, {
@@ -27,26 +37,13 @@ __export(github_exports, {
27
37
  getReleaseAssetUrl: () => getReleaseAssetUrl
28
38
  });
29
39
  module.exports = __toCommonJS(github_exports);
30
- var import_fs = require("fs");
31
- var import_promises = require("fs/promises");
32
- var import_fs2 = require("../fs.js");
40
+ var import_picomatch = __toESM(require("../external/picomatch.js"));
41
+ var import_fs = require("../fs.js");
33
42
  var import_http_request = require("../http-request.js");
34
43
  var import_logger = require("../logger.js");
35
- var import_promises2 = require("../promises.js");
44
+ var import_promises = require("../promises.js");
36
45
  var import_spawn = require("../spawn.js");
37
46
  const logger = (0, import_logger.getDefaultLogger)();
38
- let _path;
39
- // @__NO_SIDE_EFFECTS__
40
- function getPath() {
41
- if (_path === void 0) {
42
- _path = require("path");
43
- }
44
- return _path;
45
- }
46
- const SOCKET_BTM_REPO = {
47
- owner: "SocketDev",
48
- repo: "socket-btm"
49
- };
50
47
  const RETRY_CONFIG = Object.freeze({
51
48
  __proto__: null,
52
49
  // Exponential backoff: delay doubles with each retry (5s, 10s, 20s).
@@ -56,93 +53,52 @@ const RETRY_CONFIG = Object.freeze({
56
53
  // Maximum number of retry attempts (excluding initial request).
57
54
  retries: 2
58
55
  });
59
- async function downloadGitHubRelease(config) {
60
- const {
61
- assetName,
62
- binaryName,
63
- cwd = process.cwd(),
64
- downloadDir = "build/downloaded",
65
- owner,
66
- platformArch,
67
- quiet = false,
68
- removeMacOSQuarantine = true,
69
- repo,
70
- tag: explicitTag,
71
- toolName,
72
- toolPrefix
73
- } = config;
74
- let tag;
75
- if (explicitTag) {
76
- tag = explicitTag;
77
- } else if (toolPrefix) {
78
- const latestTag = await getLatestRelease(
79
- toolPrefix,
80
- { owner, repo },
81
- { quiet }
82
- );
83
- if (!latestTag) {
84
- throw new Error(`No ${toolPrefix} release found in ${owner}/${repo}`);
85
- }
86
- tag = latestTag;
87
- } else {
88
- throw new Error("Either toolPrefix or tag must be provided");
89
- }
90
- const path = /* @__PURE__ */ getPath();
91
- const resolvedDownloadDir = path.isAbsolute(downloadDir) ? downloadDir : path.join(cwd, downloadDir);
92
- const binaryDir = path.join(resolvedDownloadDir, toolName, platformArch);
93
- const binaryPath = path.join(binaryDir, binaryName);
94
- const versionPath = path.join(binaryDir, ".version");
95
- if ((0, import_fs.existsSync)(versionPath) && (0, import_fs.existsSync)(binaryPath)) {
96
- const cachedVersion = (await (0, import_promises.readFile)(versionPath, "utf8")).trim();
97
- if (cachedVersion === tag) {
98
- if (!quiet) {
99
- logger.info(`Using cached ${toolName} (${platformArch}): ${binaryPath}`);
100
- }
101
- return binaryPath;
102
- }
56
+ let _fs;
57
+ let _path;
58
+ function createMatcher(pattern) {
59
+ if (typeof pattern === "string") {
60
+ const isMatch = (0, import_picomatch.default)(pattern);
61
+ return (input) => isMatch(input);
103
62
  }
104
- if (!quiet) {
105
- logger.info(`Downloading ${toolName} for ${platformArch}...`);
63
+ if (pattern instanceof RegExp) {
64
+ return (input) => pattern.test(input);
106
65
  }
107
- await downloadReleaseAsset(
108
- tag,
109
- assetName,
110
- binaryPath,
111
- { owner, repo },
112
- { quiet }
113
- );
114
- const isWindows = binaryName.endsWith(".exe");
115
- if (!isWindows) {
116
- (0, import_fs.chmodSync)(binaryPath, 493);
117
- if (removeMacOSQuarantine && process.platform === "darwin" && platformArch.startsWith("darwin")) {
118
- try {
119
- await (0, import_spawn.spawn)("xattr", ["-d", "com.apple.quarantine", binaryPath], {
120
- stdio: "ignore"
121
- });
122
- } catch {
123
- }
124
- }
66
+ const { prefix, suffix } = pattern;
67
+ return (input) => input.startsWith(prefix) && input.endsWith(suffix);
68
+ }
69
+ // @__NO_SIDE_EFFECTS__
70
+ function getFs() {
71
+ if (_fs === void 0) {
72
+ _fs = require("fs");
125
73
  }
126
- await (0, import_promises.writeFile)(versionPath, tag, "utf8");
127
- if (!quiet) {
128
- logger.info(`Downloaded ${toolName} to ${binaryPath}`);
74
+ return _fs;
75
+ }
76
+ // @__NO_SIDE_EFFECTS__
77
+ function getPath() {
78
+ if (_path === void 0) {
79
+ _path = require("path");
129
80
  }
130
- return binaryPath;
81
+ return _path;
131
82
  }
132
- async function downloadReleaseAsset(tag, assetName, outputPath, repoConfig, options = {}) {
83
+ const SOCKET_BTM_REPO = {
84
+ owner: "SocketDev",
85
+ repo: "socket-btm"
86
+ };
87
+ async function downloadReleaseAsset(tag, assetPattern, outputPath, repoConfig, options = {}) {
133
88
  const { owner, repo } = repoConfig;
134
89
  const { quiet = false } = options;
135
90
  const downloadUrl = await getReleaseAssetUrl(
136
91
  tag,
137
- assetName,
92
+ assetPattern,
138
93
  { owner, repo },
139
94
  { quiet }
140
95
  );
141
96
  if (!downloadUrl) {
142
- throw new Error(`Asset ${assetName} not found in release ${tag}`);
97
+ const patternDesc = typeof assetPattern === "string" ? assetPattern : "matching pattern";
98
+ throw new Error(`Asset ${patternDesc} not found in release ${tag}`);
143
99
  }
144
100
  const path = /* @__PURE__ */ getPath();
145
- await (0, import_fs2.safeMkdir)(path.dirname(outputPath));
101
+ await (0, import_fs.safeMkdir)(path.dirname(outputPath));
146
102
  await (0, import_http_request.httpDownload)(downloadUrl, outputPath, {
147
103
  logger: quiet ? void 0 : logger,
148
104
  progressInterval: 10,
@@ -162,9 +118,10 @@ function getAuthHeaders() {
162
118
  return headers;
163
119
  }
164
120
  async function getLatestRelease(toolPrefix, repoConfig, options = {}) {
121
+ const { assetPattern, quiet = false } = options;
165
122
  const { owner, repo } = repoConfig;
166
- const { quiet = false } = options;
167
- return await (0, import_promises2.pRetry)(
123
+ const isMatch = assetPattern ? createMatcher(assetPattern) : void 0;
124
+ return await (0, import_promises.pRetry)(
168
125
  async () => {
169
126
  const response = await (0, import_http_request.httpRequest)(
170
127
  `https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`,
@@ -177,13 +134,22 @@ async function getLatestRelease(toolPrefix, repoConfig, options = {}) {
177
134
  }
178
135
  const releases = JSON.parse(response.body.toString("utf8"));
179
136
  for (const release of releases) {
180
- const { tag_name: tag } = release;
181
- if (tag.startsWith(toolPrefix)) {
182
- if (!quiet) {
183
- logger.info(`Found release: ${tag}`);
137
+ const { assets, tag_name: tag } = release;
138
+ if (!tag.startsWith(toolPrefix)) {
139
+ continue;
140
+ }
141
+ if (isMatch) {
142
+ const hasMatchingAsset = assets.some(
143
+ (a) => isMatch(a.name)
144
+ );
145
+ if (!hasMatchingAsset) {
146
+ continue;
184
147
  }
185
- return tag;
186
148
  }
149
+ if (!quiet) {
150
+ logger.info(`Found release: ${tag}`);
151
+ }
152
+ return tag;
187
153
  }
188
154
  if (!quiet) {
189
155
  logger.info(`No ${toolPrefix} release found in latest 100 releases`);
@@ -206,10 +172,11 @@ async function getLatestRelease(toolPrefix, repoConfig, options = {}) {
206
172
  }
207
173
  );
208
174
  }
209
- async function getReleaseAssetUrl(tag, assetName, repoConfig, options = {}) {
175
+ async function getReleaseAssetUrl(tag, assetPattern, repoConfig, options = {}) {
210
176
  const { owner, repo } = repoConfig;
211
177
  const { quiet = false } = options;
212
- return await (0, import_promises2.pRetry)(
178
+ const isMatch = typeof assetPattern === "string" && !assetPattern.includes("*") && !assetPattern.includes("{") ? (input) => input === assetPattern : createMatcher(assetPattern);
179
+ return await (0, import_promises.pRetry)(
213
180
  async () => {
214
181
  const response = await (0, import_http_request.httpRequest)(
215
182
  `https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`,
@@ -222,13 +189,14 @@ async function getReleaseAssetUrl(tag, assetName, repoConfig, options = {}) {
222
189
  }
223
190
  const release = JSON.parse(response.body.toString("utf8"));
224
191
  const asset = release.assets.find(
225
- (a) => a.name === assetName
192
+ (a) => isMatch(a.name)
226
193
  );
227
194
  if (!asset) {
228
- throw new Error(`Asset ${assetName} not found in release ${tag}`);
195
+ const patternDesc = typeof assetPattern === "string" ? assetPattern : "matching pattern";
196
+ throw new Error(`Asset ${patternDesc} not found in release ${tag}`);
229
197
  }
230
198
  if (!quiet) {
231
- logger.info(`Found asset: ${assetName}`);
199
+ logger.info(`Found asset: ${asset.name}`);
232
200
  }
233
201
  return asset.browser_download_url;
234
202
  },
@@ -248,6 +216,80 @@ async function getReleaseAssetUrl(tag, assetName, repoConfig, options = {}) {
248
216
  }
249
217
  );
250
218
  }
219
+ async function downloadGitHubRelease(config) {
220
+ const {
221
+ assetName,
222
+ binaryName,
223
+ cwd = process.cwd(),
224
+ downloadDir = "build/downloaded",
225
+ owner,
226
+ platformArch,
227
+ quiet = false,
228
+ removeMacOSQuarantine = true,
229
+ repo,
230
+ tag: explicitTag,
231
+ toolName,
232
+ toolPrefix
233
+ } = config;
234
+ let tag;
235
+ if (explicitTag) {
236
+ tag = explicitTag;
237
+ } else if (toolPrefix) {
238
+ const latestTag = await getLatestRelease(
239
+ toolPrefix,
240
+ { owner, repo },
241
+ { quiet }
242
+ );
243
+ if (!latestTag) {
244
+ throw new Error(`No ${toolPrefix} release found in ${owner}/${repo}`);
245
+ }
246
+ tag = latestTag;
247
+ } else {
248
+ throw new Error("Either toolPrefix or tag must be provided");
249
+ }
250
+ const path = /* @__PURE__ */ getPath();
251
+ const resolvedDownloadDir = path.isAbsolute(downloadDir) ? downloadDir : path.join(cwd, downloadDir);
252
+ const binaryDir = path.join(resolvedDownloadDir, toolName, platformArch);
253
+ const binaryPath = path.join(binaryDir, binaryName);
254
+ const versionPath = path.join(binaryDir, ".version");
255
+ const fs = /* @__PURE__ */ getFs();
256
+ if (fs.existsSync(versionPath) && fs.existsSync(binaryPath)) {
257
+ const cachedVersion = (await fs.promises.readFile(versionPath, "utf8")).trim();
258
+ if (cachedVersion === tag) {
259
+ if (!quiet) {
260
+ logger.info(`Using cached ${toolName} (${platformArch}): ${binaryPath}`);
261
+ }
262
+ return binaryPath;
263
+ }
264
+ }
265
+ if (!quiet) {
266
+ logger.info(`Downloading ${toolName} for ${platformArch}...`);
267
+ }
268
+ await downloadReleaseAsset(
269
+ tag,
270
+ assetName,
271
+ binaryPath,
272
+ { owner, repo },
273
+ { quiet }
274
+ );
275
+ const isWindows = binaryName.endsWith(".exe");
276
+ if (!isWindows) {
277
+ fs.chmodSync(binaryPath, 493);
278
+ if (removeMacOSQuarantine && process.platform === "darwin" && platformArch.startsWith("darwin")) {
279
+ try {
280
+ await (0, import_spawn.spawn)("xattr", ["-d", "com.apple.quarantine", binaryPath], {
281
+ stdio: "ignore"
282
+ });
283
+ } catch {
284
+ }
285
+ }
286
+ }
287
+ await fs.promises.writeFile(versionPath, tag, "utf8");
288
+ if (!quiet) {
289
+ logger.info(`Downloaded ${toolName} to ${binaryPath}`);
290
+ }
291
+ return binaryPath;
292
+ }
251
293
  // Annotate the CommonJS export names for ESM import in node:
252
294
  0 && (module.exports = {
253
295
  SOCKET_BTM_REPO,
@@ -1,57 +1,64 @@
1
+ /**
2
+ * @fileoverview Socket-btm release download utilities.
3
+ */
1
4
  import { type Arch, type Libc, type Platform } from '../constants/platform.js';
5
+ import { type AssetPattern } from './github.js';
2
6
  export type { Arch, Libc, Platform };
3
7
  /**
4
- * Configuration for downloading socket-btm binary releases.
8
+ * Configuration for downloading socket-btm generic assets.
5
9
  */
6
- export interface SocketBtmBinaryConfig {
10
+ export interface SocketBtmAssetConfig {
11
+ /** Asset name or pattern on GitHub. */
12
+ asset: string | AssetPattern;
13
+ /** @internal Discriminator fields */
14
+ bin?: never;
7
15
  /** Working directory (defaults to process.cwd()). */
8
16
  cwd?: string;
9
17
  /** Download destination directory. @default 'build/downloaded' */
10
18
  downloadDir?: string;
11
- /** Tool/package name for directory structure and release matching. */
12
- tool: string;
13
- /** Binary/executable name (without extension). @default tool */
14
- bin?: string;
15
- /** Target platform (defaults to current platform). */
16
- targetPlatform?: Platform;
17
- /** Target architecture (defaults to current arch). */
18
- targetArch?: Arch;
19
- /** Linux libc variant. Auto-detected if not specified. */
20
- libc?: Libc;
21
- /** Specific release tag to download. */
22
- tag?: string;
19
+ /** @internal Discriminator fields */
20
+ libc?: never;
21
+ /** Output filename. @default resolved asset name */
22
+ output?: string;
23
23
  /** Suppress log messages. @default false */
24
24
  quiet?: boolean;
25
- /** Remove macOS quarantine attribute after download. @default true */
25
+ /** Remove macOS quarantine attribute after download. @default false */
26
26
  removeMacOSQuarantine?: boolean;
27
- /** @internal Discriminator field */
28
- asset?: never;
27
+ /** Specific release tag to download. */
28
+ tag?: string;
29
+ /** @internal Discriminator fields */
30
+ targetArch?: never;
31
+ /** @internal Discriminator fields */
32
+ targetPlatform?: never;
33
+ /** Tool/package name for directory structure and release matching. */
34
+ tool: string;
29
35
  }
30
36
  /**
31
- * Configuration for downloading socket-btm generic assets.
37
+ * Configuration for downloading socket-btm binary releases.
32
38
  */
33
- export interface SocketBtmAssetConfig {
39
+ export interface SocketBtmBinaryConfig {
40
+ /** @internal Discriminator field */
41
+ asset?: never;
42
+ /** Binary/executable name (without extension). @default tool */
43
+ bin?: string;
34
44
  /** Working directory (defaults to process.cwd()). */
35
45
  cwd?: string;
36
46
  /** Download destination directory. @default 'build/downloaded' */
37
47
  downloadDir?: string;
38
- /** Tool/package name for directory structure and release matching. */
39
- tool: string;
40
- /** Asset name pattern on GitHub. */
41
- asset: string;
42
- /** Output filename. @default asset */
43
- output?: string;
44
- /** Specific release tag to download. */
45
- tag?: string;
48
+ /** Linux libc variant. Auto-detected if not specified. */
49
+ libc?: Libc;
46
50
  /** Suppress log messages. @default false */
47
51
  quiet?: boolean;
48
- /** Remove macOS quarantine attribute after download. @default false */
52
+ /** Remove macOS quarantine attribute after download. @default true */
49
53
  removeMacOSQuarantine?: boolean;
50
- /** @internal Discriminator fields */
51
- bin?: never;
52
- targetPlatform?: never;
53
- targetArch?: never;
54
- libc?: never;
54
+ /** Specific release tag to download. */
55
+ tag?: string;
56
+ /** Target architecture (defaults to current arch). */
57
+ targetArch?: Arch;
58
+ /** Target platform (defaults to current platform). */
59
+ targetPlatform?: Platform;
60
+ /** Tool/package name for directory structure and release matching. */
61
+ tool: string;
55
62
  }
56
63
  /**
57
64
  * Configuration for downloading socket-btm releases (binary or asset).