@socketsecurity/lib 5.7.0 → 5.8.1

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/dist/git.js CHANGED
@@ -34,6 +34,7 @@ __export(git_exports, {
34
34
  isUnstagedSync: () => isUnstagedSync
35
35
  });
36
36
  module.exports = __toCommonJS(git_exports);
37
+ var import_bin = require("./bin");
37
38
  var import_debug = require("./debug");
38
39
  var import_globs = require("./globs");
39
40
  var import_normalize = require("./paths/normalize");
@@ -56,8 +57,38 @@ function getPath() {
56
57
  return _path;
57
58
  }
58
59
  const gitDiffCache = /* @__PURE__ */ new Map();
60
+ const gitDiffAccessOrder = [];
61
+ const GIT_CACHE_MAX_SIZE = 100;
62
+ function evictLRUGitCache() {
63
+ if (gitDiffCache.size >= GIT_CACHE_MAX_SIZE && gitDiffAccessOrder.length > 0) {
64
+ const oldest = gitDiffAccessOrder.shift();
65
+ if (oldest) {
66
+ gitDiffCache.delete(oldest);
67
+ }
68
+ }
69
+ }
70
+ let _gitPath;
71
+ const realpathCache = /* @__PURE__ */ new Map();
72
+ const gitRootCache = /* @__PURE__ */ new Map();
73
+ function getCachedRealpath(pathname) {
74
+ const fs = /* @__PURE__ */ getFs();
75
+ const cached = realpathCache.get(pathname);
76
+ if (cached) {
77
+ if (fs.existsSync(cached)) {
78
+ return cached;
79
+ }
80
+ realpathCache.delete(pathname);
81
+ }
82
+ const resolved = fs.realpathSync(pathname);
83
+ realpathCache.set(pathname, resolved);
84
+ return resolved;
85
+ }
59
86
  function getGitPath() {
60
- return "git";
87
+ if (_gitPath === void 0) {
88
+ const resolved = (0, import_bin.whichSync)("git", { nothrow: true });
89
+ _gitPath = typeof resolved === "string" ? resolved : "git";
90
+ }
91
+ return _gitPath;
61
92
  }
62
93
  function getCwd() {
63
94
  return (/* @__PURE__ */ getFs()).realpathSync(process.cwd());
@@ -114,7 +145,9 @@ async function innerDiff(args, options) {
114
145
  return [];
115
146
  }
116
147
  if (cache && cacheKey) {
148
+ evictLRUGitCache();
117
149
  gitDiffCache.set(cacheKey, result);
150
+ gitDiffAccessOrder.push(cacheKey);
118
151
  }
119
152
  return result;
120
153
  }
@@ -144,18 +177,28 @@ function innerDiffSync(args, options) {
144
177
  return [];
145
178
  }
146
179
  if (cache && cacheKey) {
180
+ evictLRUGitCache();
147
181
  gitDiffCache.set(cacheKey, result);
182
+ gitDiffAccessOrder.push(cacheKey);
148
183
  }
149
184
  return result;
150
185
  }
151
186
  function findGitRoot(startPath) {
152
187
  const fs = /* @__PURE__ */ getFs();
153
188
  const path = /* @__PURE__ */ getPath();
189
+ const cached = gitRootCache.get(startPath);
190
+ if (cached) {
191
+ if (fs.existsSync(path.join(cached, ".git"))) {
192
+ return cached;
193
+ }
194
+ gitRootCache.delete(startPath);
195
+ }
154
196
  let currentPath = startPath;
155
197
  while (true) {
156
198
  try {
157
199
  const gitPath = path.join(currentPath, ".git");
158
200
  if (fs.existsSync(gitPath)) {
201
+ gitRootCache.set(startPath, currentPath);
159
202
  return currentPath;
160
203
  }
161
204
  } catch {
@@ -175,9 +218,8 @@ function parseGitDiffStdout(stdout, options, spawnCwd) {
175
218
  porcelain = false,
176
219
  ...matcherOptions
177
220
  } = { __proto__: null, ...options };
178
- const fs = /* @__PURE__ */ getFs();
179
221
  const path = /* @__PURE__ */ getPath();
180
- const cwd = cwdOption === defaultRoot ? defaultRoot : fs.realpathSync(cwdOption);
222
+ const cwd = cwdOption === defaultRoot ? defaultRoot : getCachedRealpath(cwdOption);
181
223
  const rootPath = defaultRoot;
182
224
  let rawFiles = stdout ? (0, import_strings.stripAnsi)(stdout).split("\n").map((line) => line.trimEnd()).filter((line) => line) : [];
183
225
  if (porcelain) {
@@ -241,10 +283,9 @@ async function isChanged(pathname, options) {
241
283
  ...options,
242
284
  absolute: false
243
285
  });
244
- const fs = /* @__PURE__ */ getFs();
245
286
  const path = /* @__PURE__ */ getPath();
246
- const resolvedPathname = fs.realpathSync(pathname);
247
- const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
287
+ const resolvedPathname = getCachedRealpath(pathname);
288
+ const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
248
289
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
249
290
  return files.includes(relativePath);
250
291
  }
@@ -254,12 +295,15 @@ function isChangedSync(pathname, options) {
254
295
  ...options,
255
296
  absolute: false
256
297
  });
257
- const fs = /* @__PURE__ */ getFs();
258
298
  const path = /* @__PURE__ */ getPath();
259
- const resolvedPathname = fs.realpathSync(pathname);
260
- const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
261
- const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
262
- return files.includes(relativePath);
299
+ try {
300
+ const resolvedPathname = getCachedRealpath(pathname);
301
+ const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
302
+ const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
303
+ return files.includes(relativePath);
304
+ } catch {
305
+ return false;
306
+ }
263
307
  }
264
308
  async function isUnstaged(pathname, options) {
265
309
  const files = await getUnstagedFiles({
@@ -267,10 +311,9 @@ async function isUnstaged(pathname, options) {
267
311
  ...options,
268
312
  absolute: false
269
313
  });
270
- const fs = /* @__PURE__ */ getFs();
271
314
  const path = /* @__PURE__ */ getPath();
272
- const resolvedPathname = fs.realpathSync(pathname);
273
- const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
315
+ const resolvedPathname = getCachedRealpath(pathname);
316
+ const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
274
317
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
275
318
  return files.includes(relativePath);
276
319
  }
@@ -280,10 +323,9 @@ function isUnstagedSync(pathname, options) {
280
323
  ...options,
281
324
  absolute: false
282
325
  });
283
- const fs = /* @__PURE__ */ getFs();
284
326
  const path = /* @__PURE__ */ getPath();
285
- const resolvedPathname = fs.realpathSync(pathname);
286
- const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
327
+ const resolvedPathname = getCachedRealpath(pathname);
328
+ const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
287
329
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
288
330
  return files.includes(relativePath);
289
331
  }
@@ -293,10 +335,9 @@ async function isStaged(pathname, options) {
293
335
  ...options,
294
336
  absolute: false
295
337
  });
296
- const fs = /* @__PURE__ */ getFs();
297
338
  const path = /* @__PURE__ */ getPath();
298
- const resolvedPathname = fs.realpathSync(pathname);
299
- const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
339
+ const resolvedPathname = getCachedRealpath(pathname);
340
+ const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
300
341
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
301
342
  return files.includes(relativePath);
302
343
  }
@@ -306,10 +347,9 @@ function isStagedSync(pathname, options) {
306
347
  ...options,
307
348
  absolute: false
308
349
  });
309
- const fs = /* @__PURE__ */ getFs();
310
350
  const path = /* @__PURE__ */ getPath();
311
- const resolvedPathname = fs.realpathSync(pathname);
312
- const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
351
+ const resolvedPathname = getCachedRealpath(pathname);
352
+ const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
313
353
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
314
354
  return files.includes(relativePath);
315
355
  }
package/dist/github.js CHANGED
@@ -189,15 +189,14 @@ async function fetchGhsaDetails(ghsaId, options) {
189
189
  async function cacheFetchGhsa(ghsaId, options) {
190
190
  const cache = getGithubCache();
191
191
  const key = `ghsa:${ghsaId}`;
192
- if (!process.env["DISABLE_GITHUB_CACHE"]) {
193
- const cached = await cache.get(key);
194
- if (cached) {
195
- return JSON.parse(cached);
196
- }
192
+ if (process.env["DISABLE_GITHUB_CACHE"]) {
193
+ return await fetchGhsaDetails(ghsaId, options);
197
194
  }
198
- const data = await fetchGhsaDetails(ghsaId, options);
199
- await cache.set(key, JSON.stringify(data));
200
- return data;
195
+ const cached = await cache.getOrFetch(key, async () => {
196
+ const data = await fetchGhsaDetails(ghsaId, options);
197
+ return JSON.stringify(data);
198
+ });
199
+ return JSON.parse(cached);
201
200
  }
202
201
  // Annotate the CommonJS export names for ESM import in node:
203
202
  0 && (module.exports = {
package/dist/globs.js CHANGED
@@ -107,15 +107,33 @@ function globStreamLicenses(dirname, options) {
107
107
  }
108
108
  );
109
109
  }
110
+ const MATCHER_CACHE_MAX_SIZE = 100;
110
111
  const matcherCache = /* @__PURE__ */ new Map();
112
+ const matcherAccessOrder = [];
113
+ function evictLRUMatcher() {
114
+ if (matcherCache.size >= MATCHER_CACHE_MAX_SIZE && matcherAccessOrder.length > 0) {
115
+ const oldest = matcherAccessOrder.shift();
116
+ if (oldest) {
117
+ matcherCache.delete(oldest);
118
+ }
119
+ }
120
+ }
111
121
  // @__NO_SIDE_EFFECTS__
112
122
  function getGlobMatcher(glob2, options) {
113
123
  const patterns = Array.isArray(glob2) ? glob2 : [glob2];
114
- const key = JSON.stringify({ patterns, options });
124
+ const sortedPatterns = [...patterns].sort();
125
+ const sortedOptions = options ? Object.keys(options).sort().map((k) => `${k}:${JSON.stringify(options[k])}`).join(",") : "";
126
+ const key = `${sortedPatterns.join("|")}:${sortedOptions}`;
115
127
  let matcher = matcherCache.get(key);
116
128
  if (matcher) {
129
+ const index = matcherAccessOrder.indexOf(key);
130
+ if (index !== -1) {
131
+ matcherAccessOrder.splice(index, 1);
132
+ matcherAccessOrder.push(key);
133
+ }
117
134
  return matcher;
118
135
  }
136
+ evictLRUMatcher();
119
137
  const positivePatterns = patterns.filter((p) => !p.startsWith("!"));
120
138
  const negativePatterns = patterns.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
121
139
  const matchOptions = {
@@ -129,6 +147,7 @@ function getGlobMatcher(glob2, options) {
129
147
  matchOptions
130
148
  );
131
149
  matcherCache.set(key, matcher);
150
+ matcherAccessOrder.push(key);
132
151
  return matcher;
133
152
  }
134
153
  // @__NO_SIDE_EFFECTS__
@@ -299,7 +299,7 @@ async function httpDownload(url, destPath, options) {
299
299
  } else if (logger) {
300
300
  let lastPercent = 0;
301
301
  progressCallback = (downloaded, total) => {
302
- const percent = Math.floor(downloaded / total * 100);
302
+ const percent = total === 0 ? 0 : Math.floor(downloaded / total * 100);
303
303
  if (percent >= lastPercent + progressInterval) {
304
304
  logger.log(
305
305
  ` Progress: ${percent}% (${(downloaded / 1024 / 1024).toFixed(1)} MB / ${(total / 1024 / 1024).toFixed(1)} MB)`
@@ -36,6 +36,9 @@ function memoize(fn, options = {}) {
36
36
  name = fn.name || "anonymous",
37
37
  ttl = Number.POSITIVE_INFINITY
38
38
  } = options;
39
+ if (ttl < 0) {
40
+ throw new TypeError("TTL must be non-negative");
41
+ }
39
42
  const cache = /* @__PURE__ */ new Map();
40
43
  const accessOrder = [];
41
44
  function evictLRU() {
@@ -123,7 +126,24 @@ function memoizeAsync(fn, options = {}) {
123
126
  return await cached.value;
124
127
  }
125
128
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] miss`, { key });
126
- const promise = fn(...args);
129
+ const promise = fn(...args).then(
130
+ (result) => {
131
+ const entry = cache.get(key);
132
+ if (entry) {
133
+ entry.value = Promise.resolve(result);
134
+ }
135
+ return result;
136
+ },
137
+ (error) => {
138
+ cache.delete(key);
139
+ const index = accessOrder.indexOf(key);
140
+ if (index !== -1) {
141
+ accessOrder.splice(index, 1);
142
+ }
143
+ (0, import_debug.debugLog)(`[memoizeAsync:${name}] error`, { key, error });
144
+ throw error;
145
+ }
146
+ );
127
147
  evictLRU();
128
148
  cache.set(key, {
129
149
  value: promise,
@@ -132,18 +152,7 @@ function memoizeAsync(fn, options = {}) {
132
152
  });
133
153
  accessOrder.push(key);
134
154
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] set`, { key, cacheSize: cache.size });
135
- try {
136
- const result = await promise;
137
- return result;
138
- } catch (e) {
139
- cache.delete(key);
140
- const orderIndex = accessOrder.indexOf(key);
141
- if (orderIndex !== -1) {
142
- accessOrder.splice(orderIndex, 1);
143
- }
144
- (0, import_debug.debugLog)(`[memoizeAsync:${name}] clear`, { key, reason: "error" });
145
- throw e;
146
- }
155
+ return await promise;
147
156
  };
148
157
  }
149
158
  function Memoize(options = {}) {
@@ -64,8 +64,10 @@ const packageExtensions = ObjectFreeze(
64
64
  }
65
65
  ]
66
66
  ].sort((a_, b_) => {
67
- const a = a_[0].slice(0, a_[0].lastIndexOf("@"));
68
- const b = b_[0].slice(0, b_[0].lastIndexOf("@"));
67
+ const aIndex = a_[0].lastIndexOf("@");
68
+ const bIndex = b_[0].lastIndexOf("@");
69
+ const a = aIndex === -1 ? a_[0] : a_[0].slice(0, aIndex);
70
+ const b = bIndex === -1 ? b_[0] : b_[0].slice(0, bIndex);
69
71
  if (a < b) {
70
72
  return -1;
71
73
  }
@@ -91,6 +91,9 @@ function resolveOriginalPackageName(sockRegPkgName) {
91
91
  }
92
92
  // @__NO_SIDE_EFFECTS__
93
93
  function unescapeScope(escapedScope) {
94
+ if (escapedScope.length < import_socket.REGISTRY_SCOPE_DELIMITER.length) {
95
+ return `@${escapedScope}`;
96
+ }
94
97
  return `@${escapedScope.slice(0, -import_socket.REGISTRY_SCOPE_DELIMITER.length)}`;
95
98
  }
96
99
  // Annotate the CommonJS export names for ESM import in node:
@@ -123,10 +123,10 @@ class ProcessLockManager {
123
123
  */
124
124
  isStale(lockPath, staleMs) {
125
125
  try {
126
- if (!existsSync(lockPath)) {
126
+ const stats = statSync(lockPath, { throwIfNoEntry: false });
127
+ if (!stats) {
127
128
  return false;
128
129
  }
129
- const stats = statSync(lockPath);
130
130
  const ageSeconds = Math.floor((Date.now() - stats.mtime.getTime()) / 1e3);
131
131
  const staleSeconds = Math.floor(staleMs / 1e3);
132
132
  return ageSeconds > staleSeconds;
@@ -169,7 +169,7 @@ class ProcessLockManager {
169
169
  return await (0, import_promises.pRetry)(
170
170
  async () => {
171
171
  try {
172
- if (existsSync(lockPath) && this.isStale(lockPath, staleMs)) {
172
+ if (this.isStale(lockPath, staleMs)) {
173
173
  logger.log(`Removing stale lock: ${lockPath}`);
174
174
  try {
175
175
  (0, import_fs.safeDeleteSync)(lockPath, { recursive: true });
@@ -204,7 +204,8 @@ class ProcessLockManager {
204
204
  );
205
205
  }
206
206
  if (code === "ENOTDIR") {
207
- const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
207
+ const lastSlashIndex = lockPath.lastIndexOf("/");
208
+ const parentDir = lastSlashIndex === -1 ? "." : lockPath.slice(0, lastSlashIndex);
208
209
  throw new Error(
209
210
  `Cannot create lock directory: ${lockPath}
210
211
  A path component is a file when it should be a directory.
@@ -217,7 +218,8 @@ To resolve:
217
218
  );
218
219
  }
219
220
  if (code === "ENOENT") {
220
- const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
221
+ const lastSlashIndex = lockPath.lastIndexOf("/");
222
+ const parentDir = lastSlashIndex === -1 ? "." : lockPath.slice(0, lastSlashIndex);
221
223
  throw new Error(
222
224
  `Cannot create lock directory: ${lockPath}
223
225
  Parent directory does not exist: ${parentDir}
@@ -1,3 +1,4 @@
1
+ import { type ArchiveFormat } from '../archives.js';
1
2
  /**
2
3
  * Pattern for matching release assets.
3
4
  * Can be either:
@@ -133,3 +134,42 @@ export declare function getLatestRelease(toolPrefix: string, repoConfig: RepoCon
133
134
  export declare function getReleaseAssetUrl(tag: string, assetPattern: string | AssetPattern, repoConfig: RepoConfig, options?: {
134
135
  quiet?: boolean;
135
136
  }): Promise<string | null>;
137
+ /**
138
+ * Download and extract a zip file from a GitHub release.
139
+ * Automatically handles downloading, extracting, and cleanup.
140
+ *
141
+ * @param tag - Release tag name
142
+ * @param assetPattern - Asset name or pattern (glob string, prefix/suffix object, or RegExp)
143
+ * @param outputDir - Directory to extract the zip contents to
144
+ * @param repoConfig - Repository configuration (owner/repo)
145
+ * @param options - Additional options
146
+ * @param options.quiet - Suppress log messages
147
+ * @param options.cleanup - Remove downloaded zip file after extraction (default: true)
148
+ * @returns Path to the extraction directory
149
+ */
150
+ export declare function downloadAndExtractZip(tag: string, assetPattern: string | AssetPattern, outputDir: string, repoConfig: RepoConfig, options?: {
151
+ cleanup?: boolean;
152
+ quiet?: boolean;
153
+ }): Promise<string>;
154
+ /**
155
+ * Download and extract an archive from a GitHub release.
156
+ * Supports zip, tar, tar.gz, and tgz formats.
157
+ * Automatically handles downloading, extracting, and cleanup.
158
+ *
159
+ * @param tag - Release tag name
160
+ * @param assetPattern - Asset name or pattern (glob string, prefix/suffix object, or RegExp)
161
+ * @param outputDir - Directory to extract the archive contents to
162
+ * @param repoConfig - Repository configuration (owner/repo)
163
+ * @param options - Additional options
164
+ * @param options.quiet - Suppress log messages
165
+ * @param options.cleanup - Remove downloaded archive after extraction (default: true)
166
+ * @param options.strip - Strip leading path components (like tar --strip-components)
167
+ * @param options.format - Archive format (auto-detected if not specified)
168
+ * @returns Path to the extraction directory
169
+ */
170
+ export declare function downloadAndExtractArchive(tag: string, assetPattern: string | AssetPattern, outputDir: string, repoConfig: RepoConfig, options?: {
171
+ cleanup?: boolean;
172
+ format?: ArchiveFormat;
173
+ quiet?: boolean;
174
+ strip?: number;
175
+ }): Promise<string>;
@@ -31,6 +31,8 @@ var github_exports = {};
31
31
  __export(github_exports, {
32
32
  SOCKET_BTM_REPO: () => SOCKET_BTM_REPO,
33
33
  createAssetMatcher: () => createAssetMatcher,
34
+ downloadAndExtractArchive: () => downloadAndExtractArchive,
35
+ downloadAndExtractZip: () => downloadAndExtractZip,
34
36
  downloadGitHubRelease: () => downloadGitHubRelease,
35
37
  downloadReleaseAsset: () => downloadReleaseAsset,
36
38
  getAuthHeaders: () => getAuthHeaders,
@@ -39,6 +41,7 @@ __export(github_exports, {
39
41
  });
40
42
  module.exports = __toCommonJS(github_exports);
41
43
  var import_picomatch = __toESM(require("../external/picomatch.js"));
44
+ var import_archives = require("../archives.js");
42
45
  var import_fs = require("../fs.js");
43
46
  var import_http_request = require("../http-request.js");
44
47
  var import_logger = require("../logger.js");
@@ -207,27 +210,33 @@ async function getLatestRelease(toolPrefix, repoConfig, options = {}) {
207
210
  if (!response.ok) {
208
211
  throw new Error(`Failed to fetch releases: ${response.status}`);
209
212
  }
210
- const releases = JSON.parse(response.body.toString("utf8"));
211
- const matchingReleases = releases.filter(
212
- (release) => {
213
- const { assets, tag_name: tag2 } = release;
214
- if (!tag2.startsWith(toolPrefix)) {
215
- return false;
216
- }
217
- if (!assets || assets.length === 0) {
213
+ let releases;
214
+ try {
215
+ releases = JSON.parse(response.body.toString("utf8"));
216
+ } catch (cause) {
217
+ throw new Error(
218
+ `Failed to parse GitHub releases response from https://api.github.com/repos/${owner}/${repo}/releases`,
219
+ { cause }
220
+ );
221
+ }
222
+ const matchingReleases = releases.filter((release) => {
223
+ const { assets, tag_name: tag2 } = release;
224
+ if (!tag2.startsWith(toolPrefix)) {
225
+ return false;
226
+ }
227
+ if (!assets || assets.length === 0) {
228
+ return false;
229
+ }
230
+ if (isMatch) {
231
+ const hasMatchingAsset = assets.some(
232
+ (a) => isMatch(a.name)
233
+ );
234
+ if (!hasMatchingAsset) {
218
235
  return false;
219
236
  }
220
- if (isMatch) {
221
- const hasMatchingAsset = assets.some(
222
- (a) => isMatch(a.name)
223
- );
224
- if (!hasMatchingAsset) {
225
- return false;
226
- }
227
- }
228
- return true;
229
237
  }
230
- );
238
+ return true;
239
+ });
231
240
  if (matchingReleases.length === 0) {
232
241
  if (!quiet) {
233
242
  logger.info(`No ${toolPrefix} release found in latest 100 releases`);
@@ -275,10 +284,16 @@ async function getReleaseAssetUrl(tag, assetPattern, repoConfig, options = {}) {
275
284
  if (!response.ok) {
276
285
  throw new Error(`Failed to fetch release ${tag}: ${response.status}`);
277
286
  }
278
- const release = JSON.parse(response.body.toString("utf8"));
279
- const asset = release.assets.find(
280
- (a) => isMatch(a.name)
281
- );
287
+ let release;
288
+ try {
289
+ release = JSON.parse(response.body.toString("utf8"));
290
+ } catch (cause) {
291
+ throw new Error(
292
+ `Failed to parse GitHub release response for tag ${tag}`,
293
+ { cause }
294
+ );
295
+ }
296
+ const asset = release.assets.find((a) => isMatch(a.name));
282
297
  if (!asset) {
283
298
  const patternDesc = typeof assetPattern === "string" ? assetPattern : "matching pattern";
284
299
  throw new Error(`Asset ${patternDesc} not found in release ${tag}`);
@@ -304,10 +319,95 @@ async function getReleaseAssetUrl(tag, assetPattern, repoConfig, options = {}) {
304
319
  }
305
320
  );
306
321
  }
322
+ async function downloadAndExtractZip(tag, assetPattern, outputDir, repoConfig, options = {}) {
323
+ const { cleanup = true, quiet = false } = options;
324
+ const path = /* @__PURE__ */ getPath();
325
+ const fs = /* @__PURE__ */ getFs();
326
+ await (0, import_fs.safeMkdir)(outputDir);
327
+ const zipPath = path.join(outputDir, "__temp_download__.zip");
328
+ if (!quiet) {
329
+ logger.info(`Downloading zip asset from release ${tag}...`);
330
+ }
331
+ await downloadReleaseAsset(tag, assetPattern, zipPath, repoConfig, { quiet });
332
+ if (!quiet) {
333
+ logger.info(`Extracting zip to ${outputDir}...`);
334
+ }
335
+ try {
336
+ await (0, import_archives.extractArchive)(zipPath, outputDir, { quiet });
337
+ if (!quiet) {
338
+ logger.info(`Extracted zip contents to ${outputDir}`);
339
+ }
340
+ } catch (cause) {
341
+ throw new Error(`Failed to extract zip file: ${zipPath}`, { cause });
342
+ } finally {
343
+ if (cleanup) {
344
+ try {
345
+ await fs.promises.unlink(zipPath);
346
+ if (!quiet) {
347
+ logger.info("Cleaned up temporary zip file");
348
+ }
349
+ } catch (error) {
350
+ if (!quiet) {
351
+ logger.warn(`Failed to cleanup zip file: ${error}`);
352
+ }
353
+ }
354
+ }
355
+ }
356
+ return outputDir;
357
+ }
358
+ async function downloadAndExtractArchive(tag, assetPattern, outputDir, repoConfig, options = {}) {
359
+ const { cleanup = true, format, quiet = false, strip } = options;
360
+ const path = /* @__PURE__ */ getPath();
361
+ const fs = /* @__PURE__ */ getFs();
362
+ await (0, import_fs.safeMkdir)(outputDir);
363
+ let ext = ".archive";
364
+ if (format) {
365
+ ext = format === "tar.gz" ? ".tar.gz" : `.${format}`;
366
+ } else if (typeof assetPattern === "string") {
367
+ const detectedFormat = (0, import_archives.detectArchiveFormat)(assetPattern);
368
+ if (detectedFormat) {
369
+ ext = detectedFormat === "tar.gz" ? ".tar.gz" : `.${detectedFormat}`;
370
+ }
371
+ }
372
+ const archivePath = path.join(outputDir, `__temp_download__${ext}`);
373
+ if (!quiet) {
374
+ logger.info(`Downloading archive from release ${tag}...`);
375
+ }
376
+ await downloadReleaseAsset(tag, assetPattern, archivePath, repoConfig, {
377
+ quiet
378
+ });
379
+ if (!quiet) {
380
+ logger.info(`Extracting archive to ${outputDir}...`);
381
+ }
382
+ try {
383
+ await (0, import_archives.extractArchive)(archivePath, outputDir, { quiet, strip });
384
+ if (!quiet) {
385
+ logger.info(`Extracted archive contents to ${outputDir}`);
386
+ }
387
+ } catch (cause) {
388
+ throw new Error(`Failed to extract archive: ${archivePath}`, { cause });
389
+ } finally {
390
+ if (cleanup) {
391
+ try {
392
+ await fs.promises.unlink(archivePath);
393
+ if (!quiet) {
394
+ logger.info("Cleaned up temporary archive file");
395
+ }
396
+ } catch (error) {
397
+ if (!quiet) {
398
+ logger.warn(`Failed to cleanup archive file: ${error}`);
399
+ }
400
+ }
401
+ }
402
+ }
403
+ return outputDir;
404
+ }
307
405
  // Annotate the CommonJS export names for ESM import in node:
308
406
  0 && (module.exports = {
309
407
  SOCKET_BTM_REPO,
310
408
  createAssetMatcher,
409
+ downloadAndExtractArchive,
410
+ downloadAndExtractZip,
311
411
  downloadGitHubRelease,
312
412
  downloadReleaseAsset,
313
413
  getAuthHeaders,