@socketsecurity/lib 5.6.0 → 5.8.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 (49) hide show
  1. package/CHANGELOG.md +92 -2
  2. package/README.md +190 -18
  3. package/dist/archives.d.ts +58 -0
  4. package/dist/archives.js +313 -0
  5. package/dist/arrays.js +2 -3
  6. package/dist/cache-with-ttl.js +25 -6
  7. package/dist/constants/node.js +2 -1
  8. package/dist/cover/formatters.js +5 -3
  9. package/dist/dlx/binary.d.ts +20 -0
  10. package/dist/dlx/binary.js +115 -99
  11. package/dist/dlx/detect.d.ts +8 -8
  12. package/dist/dlx/detect.js +18 -18
  13. package/dist/dlx/manifest.d.ts +32 -31
  14. package/dist/dlx/manifest.js +114 -112
  15. package/dist/dlx/package.d.ts +55 -0
  16. package/dist/dlx/package.js +90 -80
  17. package/dist/env/ci.js +1 -2
  18. package/dist/env/rewire.d.ts +33 -22
  19. package/dist/env/rewire.js +20 -7
  20. package/dist/env/socket-cli.d.ts +24 -24
  21. package/dist/env/socket-cli.js +12 -12
  22. package/dist/env/temp-dir.d.ts +6 -6
  23. package/dist/env/temp-dir.js +4 -4
  24. package/dist/env/windows.d.ts +6 -6
  25. package/dist/env/windows.js +4 -4
  26. package/dist/external/@npmcli/package-json.js +352 -824
  27. package/dist/external/adm-zip.js +2695 -0
  28. package/dist/external/debug.js +183 -7
  29. package/dist/external/external-pack.js +19 -1409
  30. package/dist/external/libnpmexec.js +2 -2
  31. package/dist/external/npm-pack.js +18777 -19997
  32. package/dist/external/pico-pack.js +29 -5
  33. package/dist/external/spdx-pack.js +41 -263
  34. package/dist/external/tar-fs.js +3053 -0
  35. package/dist/git.js +22 -4
  36. package/dist/github.js +17 -9
  37. package/dist/globs.js +20 -1
  38. package/dist/http-request.js +1 -1
  39. package/dist/memoization.js +22 -13
  40. package/dist/package-extensions.js +4 -2
  41. package/dist/packages/normalize.js +3 -0
  42. package/dist/packages/specs.js +1 -1
  43. package/dist/process-lock.js +4 -2
  44. package/dist/releases/github.d.ts +55 -4
  45. package/dist/releases/github.js +203 -101
  46. package/dist/spawn.js +1 -1
  47. package/dist/spinner.js +1 -1
  48. package/dist/stdio/progress.js +2 -2
  49. package/package.json +38 -15
package/dist/git.js CHANGED
@@ -56,6 +56,16 @@ function getPath() {
56
56
  return _path;
57
57
  }
58
58
  const gitDiffCache = /* @__PURE__ */ new Map();
59
+ const gitDiffAccessOrder = [];
60
+ const GIT_CACHE_MAX_SIZE = 100;
61
+ function evictLRUGitCache() {
62
+ if (gitDiffCache.size >= GIT_CACHE_MAX_SIZE && gitDiffAccessOrder.length > 0) {
63
+ const oldest = gitDiffAccessOrder.shift();
64
+ if (oldest) {
65
+ gitDiffCache.delete(oldest);
66
+ }
67
+ }
68
+ }
59
69
  function getGitPath() {
60
70
  return "git";
61
71
  }
@@ -114,7 +124,9 @@ async function innerDiff(args, options) {
114
124
  return [];
115
125
  }
116
126
  if (cache && cacheKey) {
127
+ evictLRUGitCache();
117
128
  gitDiffCache.set(cacheKey, result);
129
+ gitDiffAccessOrder.push(cacheKey);
118
130
  }
119
131
  return result;
120
132
  }
@@ -144,7 +156,9 @@ function innerDiffSync(args, options) {
144
156
  return [];
145
157
  }
146
158
  if (cache && cacheKey) {
159
+ evictLRUGitCache();
147
160
  gitDiffCache.set(cacheKey, result);
161
+ gitDiffAccessOrder.push(cacheKey);
148
162
  }
149
163
  return result;
150
164
  }
@@ -256,10 +270,14 @@ function isChangedSync(pathname, options) {
256
270
  });
257
271
  const fs = /* @__PURE__ */ getFs();
258
272
  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);
273
+ try {
274
+ const resolvedPathname = fs.realpathSync(pathname);
275
+ const baseCwd = options?.cwd ? fs.realpathSync(options["cwd"]) : getCwd();
276
+ const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
277
+ return files.includes(relativePath);
278
+ } catch {
279
+ return false;
280
+ }
263
281
  }
264
282
  async function isUnstaged(pathname, options) {
265
283
  const files = await getUnstagedFiles({
package/dist/github.js CHANGED
@@ -83,7 +83,16 @@ async function fetchGitHub(url, options) {
83
83
  `GitHub API error ${response.status}: ${response.statusText}`
84
84
  );
85
85
  }
86
- return JSON.parse(response.body.toString("utf8"));
86
+ try {
87
+ return JSON.parse(response.body.toString("utf8"));
88
+ } catch (error) {
89
+ throw new Error(
90
+ `Failed to parse GitHub API response: ${error instanceof Error ? error.message : String(error)}
91
+ URL: ${url}
92
+ Response may be malformed or incomplete.`,
93
+ { cause: error }
94
+ );
95
+ }
87
96
  }
88
97
  async function resolveRefToSha(owner, repo, ref, options) {
89
98
  const opts = {
@@ -180,15 +189,14 @@ async function fetchGhsaDetails(ghsaId, options) {
180
189
  async function cacheFetchGhsa(ghsaId, options) {
181
190
  const cache = getGithubCache();
182
191
  const key = `ghsa:${ghsaId}`;
183
- if (!process.env["DISABLE_GITHUB_CACHE"]) {
184
- const cached = await cache.get(key);
185
- if (cached) {
186
- return JSON.parse(cached);
187
- }
192
+ if (process.env["DISABLE_GITHUB_CACHE"]) {
193
+ return await fetchGhsaDetails(ghsaId, options);
188
194
  }
189
- const data = await fetchGhsaDetails(ghsaId, options);
190
- await cache.set(key, JSON.stringify(data));
191
- 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);
192
200
  }
193
201
  // Annotate the CommonJS export names for ESM import in node:
194
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:
@@ -43,7 +43,7 @@ var import_strings = require("../strings");
43
43
  function getRepoUrlDetails(repoUrl = "") {
44
44
  const userAndRepo = repoUrl.replace(/^.+github.com\//, "").split("/");
45
45
  const user = userAndRepo[0] || "";
46
- const project = userAndRepo.length > 1 ? userAndRepo[1]?.slice(0, -".git".length) || "" : "";
46
+ const project = userAndRepo.length > 1 ? (userAndRepo[1]?.endsWith(".git") ? userAndRepo[1].slice(0, -4) : userAndRepo[1]) || "" : "";
47
47
  return { user, project };
48
48
  }
49
49
  // @__NO_SIDE_EFFECTS__
@@ -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:
@@ -68,6 +69,24 @@ export declare const SOCKET_BTM_REPO: {
68
69
  readonly owner: "SocketDev";
69
70
  readonly repo: "socket-btm";
70
71
  };
72
+ /**
73
+ * Create a matcher function for a pattern using picomatch for glob patterns
74
+ * or simple prefix/suffix matching for object patterns.
75
+ *
76
+ * @param pattern - Pattern to match (string glob, prefix/suffix object, or RegExp)
77
+ * @returns Function that tests if a string matches the pattern
78
+ */
79
+ export declare function createAssetMatcher(pattern: string | {
80
+ prefix: string;
81
+ suffix: string;
82
+ } | RegExp): (input: string) => boolean;
83
+ /**
84
+ * Download a binary from any GitHub repository with version caching.
85
+ *
86
+ * @param config - Download configuration
87
+ * @returns Path to the downloaded binary
88
+ */
89
+ export declare function downloadGitHubRelease(config: DownloadGitHubReleaseConfig): Promise<string>;
71
90
  /**
72
91
  * Download a specific release asset.
73
92
  * Supports pattern matching for dynamic asset discovery.
@@ -116,9 +135,41 @@ export declare function getReleaseAssetUrl(tag: string, assetPattern: string | A
116
135
  quiet?: boolean;
117
136
  }): Promise<string | null>;
118
137
  /**
119
- * Download a binary from any GitHub repository with version caching.
138
+ * Download and extract a zip file from a GitHub release.
139
+ * Automatically handles downloading, extracting, and cleanup.
120
140
  *
121
- * @param config - Download configuration
122
- * @returns Path to the downloaded binary
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
123
149
  */
124
- export declare function downloadGitHubRelease(config: DownloadGitHubReleaseConfig): Promise<string>;
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>;