@socketsecurity/lib 5.26.0 → 5.26.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.
Files changed (147) hide show
  1. package/CHANGELOG.md +345 -1185
  2. package/README.md +1 -1
  3. package/dist/abort.js +7 -6
  4. package/dist/agent.js +16 -15
  5. package/dist/ansi.js +3 -2
  6. package/dist/archives.js +18 -17
  7. package/dist/argv/flags.js +29 -28
  8. package/dist/argv/parse.js +4 -3
  9. package/dist/arrays.js +3 -2
  10. package/dist/bin.js +12 -11
  11. package/dist/cacache.js +16 -11
  12. package/dist/cache-with-ttl.js +26 -21
  13. package/dist/colors.js +2 -1
  14. package/dist/constants/licenses.js +2 -1
  15. package/dist/constants/lifecycle-script-names.js +2 -1
  16. package/dist/constants/maintained-node-versions.js +2 -1
  17. package/dist/constants/node.js +4 -3
  18. package/dist/constants/package-default-socket-categories.js +2 -1
  19. package/dist/constants/packages.js +4 -3
  20. package/dist/constants/socket.js +1 -1
  21. package/dist/cover/code.js +9 -8
  22. package/dist/cover/formatters.js +12 -11
  23. package/dist/cover/type.js +5 -4
  24. package/dist/crypto.d.ts +36 -0
  25. package/dist/{effects/ultra.js → crypto.js} +33 -37
  26. package/dist/debug.js +12 -15
  27. package/dist/dlx/arborist.js +10 -9
  28. package/dist/dlx/binary.js +27 -24
  29. package/dist/dlx/cache.js +2 -10
  30. package/dist/dlx/detect.js +6 -5
  31. package/dist/dlx/integrity.js +13 -11
  32. package/dist/dlx/lockfile.js +7 -6
  33. package/dist/dlx/manifest.js +6 -5
  34. package/dist/dlx/package.js +17 -16
  35. package/dist/dlx/packages.js +6 -5
  36. package/dist/dlx/paths.d.ts +1 -1
  37. package/dist/dlx/paths.js +2 -1
  38. package/dist/effects/pulse-frames.js +4 -3
  39. package/dist/effects/shimmer-keyframes.d.ts +62 -0
  40. package/dist/effects/shimmer-keyframes.js +55 -0
  41. package/dist/effects/shimmer-terminal.d.ts +66 -0
  42. package/dist/effects/shimmer-terminal.js +57 -0
  43. package/dist/effects/shimmer.d.ts +293 -0
  44. package/dist/effects/shimmer.js +180 -0
  45. package/dist/env/rewire.js +4 -3
  46. package/dist/env.js +5 -4
  47. package/dist/errors.js +1 -1
  48. package/dist/external/@npmcli/package-json/lib/read-package.js +10 -10
  49. package/dist/external/@npmcli/package-json.js +372 -372
  50. package/dist/external/@npmcli/promise-spawn.js +24 -24
  51. package/dist/external/@socketregistry/packageurl-js.js +19 -18
  52. package/dist/external/@socketregistry/yocto-spinner.js +4 -3
  53. package/dist/external/@yarnpkg/extensions.js +2 -1
  54. package/dist/external/adm-zip.js +70 -70
  55. package/dist/external/debug.js +22 -22
  56. package/dist/external/external-pack.js +45 -45
  57. package/dist/external/fast-sort.js +5 -5
  58. package/dist/external/libnpmexec.js +4 -3
  59. package/dist/external/npm-pack.js +369 -369
  60. package/dist/external/p-map.js +9 -9
  61. package/dist/external/pico-pack.js +190 -190
  62. package/dist/external/pony-cause.js +2 -1
  63. package/dist/external/spdx-pack.js +11 -10
  64. package/dist/external/tar-fs.js +58 -58
  65. package/dist/external/which.js +13 -13
  66. package/dist/external/yargs-parser.js +71 -71
  67. package/dist/fs.js +19 -23
  68. package/dist/git.js +14 -13
  69. package/dist/github.d.ts +34 -0
  70. package/dist/github.js +248 -23
  71. package/dist/globs.d.ts +20 -0
  72. package/dist/globs.js +134 -25
  73. package/dist/http-request.d.ts +4 -3
  74. package/dist/http-request.js +39 -32
  75. package/dist/ipc-cli.js +2 -1
  76. package/dist/ipc.js +5 -4
  77. package/dist/json/edit.js +4 -3
  78. package/dist/json/format.js +3 -2
  79. package/dist/json/parse.js +7 -6
  80. package/dist/links.js +2 -1
  81. package/dist/logger.js +21 -14
  82. package/dist/memoization.js +16 -15
  83. package/dist/objects.js +2 -2
  84. package/dist/packages/edit.js +8 -7
  85. package/dist/packages/exports.js +17 -16
  86. package/dist/packages/isolation.js +20 -13
  87. package/dist/packages/licenses.js +6 -3
  88. package/dist/packages/manifest.js +2 -1
  89. package/dist/packages/normalize.js +8 -4
  90. package/dist/packages/operations.js +5 -4
  91. package/dist/packages/provenance.js +7 -6
  92. package/dist/packages/specs.js +2 -1
  93. package/dist/packages/validation.js +2 -1
  94. package/dist/paths/normalize.js +32 -40
  95. package/dist/paths/packages.js +2 -1
  96. package/dist/paths/rewire.js +3 -2
  97. package/dist/performance.js +19 -18
  98. package/dist/primordials.d.ts +9 -0
  99. package/dist/primordials.js +17 -0
  100. package/dist/process-lock.js +14 -13
  101. package/dist/promise-queue.js +17 -9
  102. package/dist/promises.d.ts +29 -6
  103. package/dist/promises.js +19 -15
  104. package/dist/regexps.js +9 -8
  105. package/dist/releases/github-api.d.ts +56 -0
  106. package/dist/releases/github-api.js +275 -0
  107. package/dist/releases/github-archives.d.ts +60 -0
  108. package/dist/releases/github-archives.js +136 -0
  109. package/dist/releases/github-assets.d.ts +21 -0
  110. package/dist/releases/github-assets.js +52 -0
  111. package/dist/releases/github-auth.d.ts +16 -0
  112. package/dist/releases/github-auth.js +51 -0
  113. package/dist/releases/github-downloads.d.ts +42 -0
  114. package/dist/releases/github-downloads.js +155 -0
  115. package/dist/releases/github-types.d.ts +66 -0
  116. package/dist/{effects/types.js → releases/github-types.js} +2 -2
  117. package/dist/releases/socket-btm.d.ts +1 -1
  118. package/dist/releases/socket-btm.js +17 -17
  119. package/dist/schema/parse.js +2 -1
  120. package/dist/schema/validate.js +6 -5
  121. package/dist/shadow.js +2 -1
  122. package/dist/signal-exit.js +2 -2
  123. package/dist/spawn.js +45 -35
  124. package/dist/spinner.d.ts +13 -6
  125. package/dist/spinner.js +75 -57
  126. package/dist/ssri.js +8 -7
  127. package/dist/stdio/footer.js +13 -12
  128. package/dist/stdio/header.js +5 -4
  129. package/dist/stdio/progress.js +13 -12
  130. package/dist/strings.js +19 -18
  131. package/dist/suppress-warnings.js +2 -2
  132. package/dist/tables.js +16 -13
  133. package/dist/temporary-executor.js +2 -1
  134. package/dist/themes/context.js +2 -1
  135. package/dist/themes/types.d.ts +1 -1
  136. package/dist/themes/utils.d.ts +8 -3
  137. package/dist/themes/utils.js +29 -5
  138. package/dist/url.js +4 -3
  139. package/package.json +44 -20
  140. package/dist/effects/text-shimmer.d.ts +0 -58
  141. package/dist/effects/text-shimmer.js +0 -192
  142. package/dist/effects/types.d.ts +0 -47
  143. package/dist/effects/ultra.d.ts +0 -22
  144. package/dist/releases/github.d.ts +0 -234
  145. package/dist/releases/github.js +0 -417
  146. package/dist/themes/index.d.ts +0 -49
  147. package/dist/themes/index.js +0 -60
package/dist/git.js CHANGED
@@ -52,13 +52,14 @@ var import_globs = require("./globs");
52
52
  var import_normalize = require("./paths/normalize");
53
53
  var import_spawn = require("./spawn");
54
54
  var import_strings = require("./strings");
55
+ var import_primordials = require("./primordials");
55
56
  let _fs;
56
57
  let _path;
57
- const gitDiffCache = /* @__PURE__ */ new Map();
58
+ const gitDiffCache = new import_primordials.MapCtor();
58
59
  const GIT_CACHE_MAX_SIZE = 100;
59
60
  let _gitPath;
60
- const realpathCache = /* @__PURE__ */ new Map();
61
- const gitRootCache = /* @__PURE__ */ new Map();
61
+ const realpathCache = new import_primordials.MapCtor();
62
+ const gitRootCache = new import_primordials.MapCtor();
62
63
  function getCachedGitDiff(key) {
63
64
  const result = gitDiffCache.get(key);
64
65
  if (result) {
@@ -68,7 +69,7 @@ function getCachedGitDiff(key) {
68
69
  return result;
69
70
  }
70
71
  function stableKey(value) {
71
- return JSON.stringify(value, (_key, val) => {
72
+ return (0, import_primordials.JSONStringify)(value, (_key, val) => {
72
73
  if (val && typeof val === "object" && !Array.isArray(val)) {
73
74
  const sorted = {};
74
75
  for (const k of Object.keys(val).sort()) {
@@ -166,7 +167,7 @@ async function innerDiff(args, options) {
166
167
  ...args[2],
167
168
  stdioString: false
168
169
  });
169
- const stdout = Buffer.isBuffer(spawnResult.stdout) ? spawnResult.stdout.toString("utf8") : String(spawnResult.stdout);
170
+ const stdout = (0, import_primordials.BufferIsBuffer)(spawnResult.stdout) ? spawnResult.stdout.toString("utf8") : String(spawnResult.stdout);
170
171
  const spawnCwd = typeof args[2]["cwd"] === "string" ? args[2]["cwd"] : void 0;
171
172
  result = parseGitDiffStdout(stdout, parseOptions, spawnCwd);
172
173
  } catch (e) {
@@ -196,7 +197,7 @@ function innerDiffSync(args, options) {
196
197
  ...args[2],
197
198
  stdioString: false
198
199
  });
199
- const stdout = Buffer.isBuffer(spawnResult.stdout) ? spawnResult.stdout.toString("utf8") : String(spawnResult.stdout);
200
+ const stdout = (0, import_primordials.BufferIsBuffer)(spawnResult.stdout) ? spawnResult.stdout.toString("utf8") : String(spawnResult.stdout);
200
201
  const spawnCwd = typeof args[2]["cwd"] === "string" ? args[2]["cwd"] : void 0;
201
202
  result = parseGitDiffStdout(stdout, parseOptions, spawnCwd);
202
203
  } catch (e) {
@@ -225,7 +226,7 @@ function parseGitDiffStdout(stdout, options, spawnCwd) {
225
226
  let rawFiles = stdout ? (0, import_strings.stripAnsi)(stdout).split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line) : [];
226
227
  if (porcelain) {
227
228
  rawFiles = rawFiles.map((line) => {
228
- return line.length > 3 ? line.substring(3) : line;
229
+ return line.length > 3 ? (0, import_primordials.StringPrototypeSubstring)(line, 3) : line;
229
230
  });
230
231
  }
231
232
  const files = absolute ? rawFiles.map((relPath2) => (0, import_normalize.normalizePath)(path.join(rootPath, relPath2))) : rawFiles.map((relPath2) => (0, import_normalize.normalizePath)(relPath2));
@@ -324,7 +325,7 @@ async function isChanged(pathname, options) {
324
325
  const resolvedPathname = getCachedRealpath(pathname);
325
326
  const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
326
327
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
327
- return files.includes(relativePath);
328
+ return (0, import_primordials.ArrayPrototypeIncludes)(files, relativePath);
328
329
  }
329
330
  function isChangedSync(pathname, options) {
330
331
  const files = getChangedFilesSync({
@@ -337,7 +338,7 @@ function isChangedSync(pathname, options) {
337
338
  const resolvedPathname = getCachedRealpath(pathname);
338
339
  const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
339
340
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
340
- return files.includes(relativePath);
341
+ return (0, import_primordials.ArrayPrototypeIncludes)(files, relativePath);
341
342
  } catch {
342
343
  return false;
343
344
  }
@@ -352,7 +353,7 @@ async function isStaged(pathname, options) {
352
353
  const resolvedPathname = getCachedRealpath(pathname);
353
354
  const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
354
355
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
355
- return files.includes(relativePath);
356
+ return (0, import_primordials.ArrayPrototypeIncludes)(files, relativePath);
356
357
  }
357
358
  function isStagedSync(pathname, options) {
358
359
  const files = getStagedFilesSync({
@@ -364,7 +365,7 @@ function isStagedSync(pathname, options) {
364
365
  const resolvedPathname = getCachedRealpath(pathname);
365
366
  const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
366
367
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
367
- return files.includes(relativePath);
368
+ return (0, import_primordials.ArrayPrototypeIncludes)(files, relativePath);
368
369
  }
369
370
  async function isUnstaged(pathname, options) {
370
371
  const files = await getUnstagedFiles({
@@ -376,7 +377,7 @@ async function isUnstaged(pathname, options) {
376
377
  const resolvedPathname = getCachedRealpath(pathname);
377
378
  const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
378
379
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
379
- return files.includes(relativePath);
380
+ return (0, import_primordials.ArrayPrototypeIncludes)(files, relativePath);
380
381
  }
381
382
  function isUnstagedSync(pathname, options) {
382
383
  const files = getUnstagedFilesSync({
@@ -388,7 +389,7 @@ function isUnstagedSync(pathname, options) {
388
389
  const resolvedPathname = getCachedRealpath(pathname);
389
390
  const baseCwd = options?.cwd ? getCachedRealpath(options["cwd"]) : getCwd();
390
391
  const relativePath = (0, import_normalize.normalizePath)(path.relative(baseCwd, resolvedPathname));
391
- return files.includes(relativePath);
392
+ return (0, import_primordials.ArrayPrototypeIncludes)(files, relativePath);
392
393
  }
393
394
  // Annotate the CommonJS export names for ESM import in node:
394
395
  0 && (module.exports = {
package/dist/github.d.ts CHANGED
@@ -21,6 +21,40 @@
21
21
  * - Cache to minimize API calls
22
22
  */
23
23
  import type { SpawnOptions } from './spawn';
24
+ /**
25
+ * Thrown by `fetchGitHub` when GitHub returns HTTP 200 OK with a
26
+ * zero-byte body — the "successful empty response" pattern.
27
+ *
28
+ * Why this exists (background for new contributors):
29
+ * GitHub's REST API has a documented failure mode that is *very*
30
+ * easy to miss in code review. During incidents where the search
31
+ * / Elasticsearch backing index is degraded (see GitHub status
32
+ * pages with titles like "search is degraded" or "Pull Requests
33
+ * degraded"), the REST `/repos/...` GET endpoints return:
34
+ * - HTTP status: 200 OK ← looks like success
35
+ * - Body: "" ← but the payload is empty
36
+ * - Headers: no Retry-After, no rate-limit signal, nothing
37
+ *
38
+ * Without a typed error, calling code does
39
+ * `JSON.parse(response.body.toString('utf8'))`
40
+ * on an empty string, which throws a confusing
41
+ * `SyntaxError: Unexpected end of JSON input`. That error has
42
+ * nothing to do with our code — but it's the only signal upstream
43
+ * sees. This class wraps that case in a *named* error so callers
44
+ * can `instanceof GitHubEmptyBodyError` and choose what to do:
45
+ * retry the same endpoint later, fall back to GraphQL (which uses
46
+ * a different backend and is unaffected by ES outages), or surface
47
+ * a clean message to the user.
48
+ *
49
+ * The HTTP status is hard-coded to 200 because that's *exactly*
50
+ * what makes this insidious — a real 4xx/5xx would already be
51
+ * handled by the rate-limit / status-code branch above.
52
+ */
53
+ export declare class GitHubEmptyBodyError extends Error {
54
+ /** HTTP status (always 200 — that's what makes this case insidious). */
55
+ status: number;
56
+ constructor(url: string);
57
+ }
24
58
  /**
25
59
  * Options for GitHub API fetch requests.
26
60
  */
package/dist/github.js CHANGED
@@ -30,6 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
31
  var github_exports = {};
32
32
  __export(github_exports, {
33
+ GitHubEmptyBodyError: () => GitHubEmptyBodyError,
33
34
  cacheFetchGhsa: () => cacheFetchGhsa,
34
35
  clearRefCache: () => clearRefCache,
35
36
  fetchGhsaDetails: () => fetchGhsaDetails,
@@ -47,14 +48,32 @@ var import_github = require("./env/github");
47
48
  var import_socket_cli = require("./env/socket-cli");
48
49
  var import_errors = require("./errors");
49
50
  var import_http_request = require("./http-request");
51
+ var import_primordials = require("./primordials");
50
52
  var import_spawn = require("./spawn");
51
53
  const GITHUB_API_BASE_URL = "https://api.github.com";
54
+ const GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
52
55
  const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
53
56
  let _githubCache;
57
+ class GitHubEmptyBodyError extends Error {
58
+ /** HTTP status (always 200 — that's what makes this case insidious). */
59
+ status;
60
+ constructor(url) {
61
+ super(`GitHub API returned HTTP 200 with empty body: ${url}`);
62
+ this.name = "GitHubEmptyBodyError";
63
+ this.status = 200;
64
+ }
65
+ }
54
66
  async function fetchRefSha(owner, repo, ref, options) {
55
67
  const fetchOptions = {
56
68
  token: options.token
57
69
  };
70
+ let sawEmptyBody = false;
71
+ const note404 = (e) => {
72
+ if (e instanceof GitHubEmptyBodyError) {
73
+ sawEmptyBody = true;
74
+ }
75
+ return e;
76
+ };
58
77
  try {
59
78
  const tagUrl = `${GITHUB_API_BASE_URL}/repos/${owner}/${repo}/git/refs/tags/${ref}`;
60
79
  const tagData = await fetchGitHub(tagUrl, fetchOptions);
@@ -66,12 +85,14 @@ async function fetchRefSha(owner, repo, ref, options) {
66
85
  return tagObject.object.sha;
67
86
  }
68
87
  return tagData.object.sha;
69
- } catch {
88
+ } catch (e) {
89
+ note404(e);
70
90
  try {
71
91
  const branchUrl = `${GITHUB_API_BASE_URL}/repos/${owner}/${repo}/git/refs/heads/${ref}`;
72
92
  const branchData = await fetchGitHub(branchUrl, fetchOptions);
73
93
  return branchData.object.sha;
74
- } catch {
94
+ } catch (e2) {
95
+ note404(e2);
75
96
  try {
76
97
  const commitUrl = `${GITHUB_API_BASE_URL}/repos/${owner}/${repo}/commits/${ref}`;
77
98
  const commitData = await fetchGitHub(
@@ -79,14 +100,114 @@ async function fetchRefSha(owner, repo, ref, options) {
79
100
  fetchOptions
80
101
  );
81
102
  return commitData.sha;
82
- } catch (e) {
83
- throw new Error(
84
- `failed to resolve ref "${ref}" for ${owner}/${repo}: ${(0, import_errors.errorMessage)(e)}`
103
+ } catch (e3) {
104
+ note404(e3);
105
+ if (sawEmptyBody) {
106
+ let graphqlSha;
107
+ let graphqlErr;
108
+ try {
109
+ graphqlSha = await fetchRefShaViaGraphQL(
110
+ owner,
111
+ repo,
112
+ ref,
113
+ fetchOptions
114
+ );
115
+ } catch (cause) {
116
+ graphqlErr = cause;
117
+ }
118
+ if (graphqlSha) {
119
+ return graphqlSha;
120
+ }
121
+ if (graphqlErr !== void 0) {
122
+ throw new import_primordials.ErrorCtor(
123
+ `Failed to resolve ref "${ref}" for ${owner}/${repo}: both REST and GraphQL backends degraded`,
124
+ { cause: graphqlErr }
125
+ );
126
+ }
127
+ }
128
+ throw new import_primordials.ErrorCtor(
129
+ `Failed to resolve ref "${ref}" for ${owner}/${repo}: ${(0, import_errors.errorMessage)(e3)}`
85
130
  );
86
131
  }
87
132
  }
88
133
  }
89
134
  }
135
+ async function fetchRefShaViaGraphQL(owner, repo, ref, options) {
136
+ const token = options.token || getGitHubToken();
137
+ const headers = {
138
+ Accept: "application/vnd.github.v3+json",
139
+ "Content-Type": "application/json",
140
+ "User-Agent": "socket-registry-github-client",
141
+ ...options.headers
142
+ };
143
+ if (token) {
144
+ headers["Authorization"] = `Bearer ${token}`;
145
+ }
146
+ const query = `query($owner: String!, $repo: String!, $tag: String!, $branch: String!, $oid: GitObjectID!) {
147
+ repository(owner: $owner, name: $repo) {
148
+ tagRef: ref(qualifiedName: $tag) {
149
+ target {
150
+ __typename
151
+ ... on Tag { target { oid } }
152
+ ... on Commit { oid }
153
+ }
154
+ }
155
+ branchRef: ref(qualifiedName: $branch) {
156
+ target { oid }
157
+ }
158
+ commit: object(oid: $oid) {
159
+ __typename
160
+ ... on Commit { oid }
161
+ }
162
+ }
163
+ }`;
164
+ const looksLikeSha = /^[a-f0-9]{40}$/i.test(ref);
165
+ const oidArg = looksLikeSha ? ref : "0000000000000000000000000000000000000000";
166
+ const response = await (0, import_http_request.httpRequest)(GITHUB_GRAPHQL_URL, {
167
+ body: (0, import_primordials.JSONStringify)({
168
+ query,
169
+ variables: {
170
+ branch: `refs/heads/${ref}`,
171
+ oid: oidArg,
172
+ owner,
173
+ repo,
174
+ tag: `refs/tags/${ref}`
175
+ }
176
+ }),
177
+ headers,
178
+ method: "POST"
179
+ });
180
+ if (!response.ok || response.body.byteLength === 0) {
181
+ return void 0;
182
+ }
183
+ let parsed;
184
+ try {
185
+ parsed = (0, import_primordials.JSONParse)(response.body.toString("utf8"));
186
+ } catch {
187
+ return void 0;
188
+ }
189
+ const repoData = parsed.data?.repository;
190
+ if (!repoData) {
191
+ return void 0;
192
+ }
193
+ const tagTarget = repoData.tagRef?.target;
194
+ if (tagTarget) {
195
+ if (tagTarget.__typename === "Tag") {
196
+ return tagTarget.target?.oid ?? void 0;
197
+ }
198
+ if (tagTarget.__typename === "Commit") {
199
+ return tagTarget.oid ?? void 0;
200
+ }
201
+ }
202
+ const branchOid = repoData.branchRef?.target?.oid;
203
+ if (branchOid) {
204
+ return branchOid;
205
+ }
206
+ if (repoData.commit?.__typename === "Commit" && repoData.commit.oid) {
207
+ return repoData.commit.oid;
208
+ }
209
+ return void 0;
210
+ }
90
211
  function getGithubCache() {
91
212
  if (_githubCache === void 0) {
92
213
  _githubCache = (0, import_cache_with_ttl.createTtlCache)({
@@ -114,20 +235,120 @@ async function clearRefCache() {
114
235
  }
115
236
  async function fetchGhsaDetails(ghsaId, options) {
116
237
  const url = `https://api.github.com/advisories/${ghsaId}`;
117
- const data = await fetchGitHub(url, options);
238
+ try {
239
+ const data = await fetchGitHub(url, options);
240
+ return {
241
+ ghsaId: data.ghsa_id,
242
+ summary: data.summary,
243
+ details: data.details,
244
+ severity: data.severity,
245
+ aliases: data.aliases || [],
246
+ publishedAt: data.published_at,
247
+ updatedAt: data.updated_at,
248
+ withdrawnAt: data.withdrawn_at,
249
+ references: data.references || [],
250
+ vulnerabilities: data.vulnerabilities || [],
251
+ cvss: data.cvss,
252
+ cwes: data.cwes || []
253
+ };
254
+ } catch (e) {
255
+ if (e instanceof GitHubEmptyBodyError) {
256
+ try {
257
+ return await fetchGhsaDetailsViaGraphQL(ghsaId, options);
258
+ } catch (cause) {
259
+ throw new import_primordials.ErrorCtor(
260
+ `Failed to fetch advisory ${ghsaId}: both REST and GraphQL backends degraded`,
261
+ { cause }
262
+ );
263
+ }
264
+ }
265
+ throw e;
266
+ }
267
+ }
268
+ async function fetchGhsaDetailsViaGraphQL(ghsaId, options) {
269
+ const opts = { __proto__: null, ...options };
270
+ const token = opts.token || getGitHubToken();
271
+ const headers = {
272
+ Accept: "application/vnd.github.v3+json",
273
+ "Content-Type": "application/json",
274
+ "User-Agent": "socket-registry-github-client",
275
+ ...opts.headers
276
+ };
277
+ if (token) {
278
+ headers["Authorization"] = `Bearer ${token}`;
279
+ }
280
+ const query = `query($ghsaId: String!) {
281
+ securityAdvisory(ghsaId: $ghsaId) {
282
+ ghsaId
283
+ summary
284
+ description
285
+ severity
286
+ publishedAt
287
+ updatedAt
288
+ withdrawnAt
289
+ cvss { score vectorString }
290
+ cwes(first: 50) { nodes { cweId name description } }
291
+ references { url }
292
+ vulnerabilities(first: 100) {
293
+ nodes {
294
+ package { ecosystem name }
295
+ vulnerableVersionRange
296
+ firstPatchedVersion { identifier }
297
+ }
298
+ }
299
+ identifiers { type value }
300
+ }
301
+ }`;
302
+ const response = await (0, import_http_request.httpRequest)(GITHUB_GRAPHQL_URL, {
303
+ body: (0, import_primordials.JSONStringify)({ query, variables: { ghsaId } }),
304
+ headers,
305
+ method: "POST"
306
+ });
307
+ if (!response.ok) {
308
+ throw new import_primordials.ErrorCtor(
309
+ `GitHub GraphQL API error ${response.status}: ${response.statusText}`
310
+ );
311
+ }
312
+ if (response.body.byteLength === 0) {
313
+ throw new GitHubEmptyBodyError(GITHUB_GRAPHQL_URL);
314
+ }
315
+ let parsed;
316
+ try {
317
+ parsed = (0, import_primordials.JSONParse)(response.body.toString("utf8"));
318
+ } catch (cause) {
319
+ throw new import_primordials.ErrorCtor(
320
+ `Failed to parse GitHub GraphQL response for advisory ${ghsaId}`,
321
+ { cause }
322
+ );
323
+ }
324
+ if (parsed.errors?.length) {
325
+ throw new import_primordials.ErrorCtor(
326
+ `GraphQL securityAdvisory(${ghsaId}) returned errors: ${parsed.errors.map((e) => e.message).join("; ")}`
327
+ );
328
+ }
329
+ const adv = parsed.data?.securityAdvisory;
330
+ if (!adv) {
331
+ throw new import_primordials.ErrorCtor(`GHSA ${ghsaId} not found`);
332
+ }
118
333
  return {
119
- ghsaId: data.ghsa_id,
120
- summary: data.summary,
121
- details: data.details,
122
- severity: data.severity,
123
- aliases: data.aliases || [],
124
- publishedAt: data.published_at,
125
- updatedAt: data.updated_at,
126
- withdrawnAt: data.withdrawn_at,
127
- references: data.references || [],
128
- vulnerabilities: data.vulnerabilities || [],
129
- cvss: data.cvss,
130
- cwes: data.cwes || []
334
+ ghsaId: adv.ghsaId,
335
+ summary: adv.summary,
336
+ details: adv.description,
337
+ // REST returns severity lowercase ("moderate"); GraphQL uppercases
338
+ // ("MODERATE"). Normalize so callers can compare against a single
339
+ // canonical form regardless of which transport ran.
340
+ severity: adv.severity.toLowerCase(),
341
+ // REST `aliases` is the list of non-GHSA identifiers (CVE ids,
342
+ // typically). GraphQL `identifiers` includes the advisory's own
343
+ // GHSA id alongside CVE ids; filter it out to match REST shape.
344
+ aliases: adv.identifiers?.filter((i) => i.type !== "GHSA").map((i) => i.value) ?? [],
345
+ publishedAt: adv.publishedAt,
346
+ updatedAt: adv.updatedAt,
347
+ withdrawnAt: adv.withdrawnAt ?? "",
348
+ references: adv.references ?? [],
349
+ vulnerabilities: adv.vulnerabilities?.nodes ?? [],
350
+ cvss: adv.cvss ?? null,
351
+ cwes: adv.cwes?.nodes ?? []
131
352
  };
132
353
  }
133
354
  async function fetchGitHub(url, options) {
@@ -149,8 +370,8 @@ async function fetchGitHub(url, options) {
149
370
  if (rateLimitStr === "0") {
150
371
  const resetTime = response.headers["x-ratelimit-reset"];
151
372
  const resetTimeStr = typeof resetTime === "string" ? resetTime : resetTime?.[0];
152
- const resetDate = resetTimeStr ? new Date(Number(resetTimeStr) * 1e3) : void 0;
153
- const error = new Error(
373
+ const resetDate = resetTimeStr ? new import_primordials.DateCtor(Number(resetTimeStr) * 1e3) : void 0;
374
+ const error = new import_primordials.ErrorCtor(
154
375
  `GitHub API rate limit exceeded${resetDate ? `. Resets at ${resetDate.toLocaleString()}` : ""}. Use GITHUB_TOKEN environment variable to increase rate limit.`
155
376
  );
156
377
  error.status = 403;
@@ -158,14 +379,17 @@ async function fetchGitHub(url, options) {
158
379
  throw error;
159
380
  }
160
381
  }
161
- throw new Error(
382
+ throw new import_primordials.ErrorCtor(
162
383
  `GitHub API error ${response.status}: ${response.statusText}`
163
384
  );
164
385
  }
386
+ if (response.body.byteLength === 0) {
387
+ throw new GitHubEmptyBodyError(url);
388
+ }
165
389
  try {
166
- return JSON.parse(response.body.toString("utf8"));
390
+ return (0, import_primordials.JSONParse)(response.body.toString("utf8"));
167
391
  } catch (e) {
168
- throw new Error(
392
+ throw new import_primordials.ErrorCtor(
169
393
  `Failed to parse GitHub API response: ${(0, import_errors.errorMessage)(e)}
170
394
  URL: ${url}
171
395
  Response may be malformed or incomplete.`,
@@ -211,6 +435,7 @@ async function resolveRefToSha(owner, repo, ref, options) {
211
435
  }
212
436
  // Annotate the CommonJS export names for ESM import in node:
213
437
  0 && (module.exports = {
438
+ GitHubEmptyBodyError,
214
439
  cacheFetchGhsa,
215
440
  clearRefCache,
216
441
  fetchGhsaDetails,
package/dist/globs.d.ts CHANGED
@@ -32,6 +32,16 @@ export interface GlobOptions extends FastGlobOptions {
32
32
  recursive?: boolean;
33
33
  }
34
34
  export type { Pattern, FastGlobOptions };
35
+ /**
36
+ * Resolve `path.matchesGlob` (or `undefined` if the runtime predates
37
+ * it). Probes once and caches the result for every subsequent call.
38
+ *
39
+ * Used by `getGlobMatcher`'s narrow fast-path — see the conditions
40
+ * spelled out at the call site. Exported for unit tests.
41
+ *
42
+ * @internal
43
+ */
44
+ export declare function getMatchesGlob(): ((p: string, pattern: string) => boolean) | undefined;
35
45
  export declare const defaultIgnore: readonly string[];
36
46
  /**
37
47
  * Return a glob-matcher function, memoized by pattern + options.
@@ -73,6 +83,16 @@ export declare function getGlobMatcher(glob: Pattern | Pattern[], options?: {
73
83
  * console.log(files) // ['src/index.ts', 'src/utils.ts']
74
84
  * ```
75
85
  */
86
+ /**
87
+ * Whether the caller's option bag is fully expressible with
88
+ * `node:fs.glob` (`cwd` + `exclude`). Any other option means we must
89
+ * fall back to fast-glob, which exposes the wider surface.
90
+ *
91
+ * Exported for unit tests; not part of the public API.
92
+ *
93
+ * @internal
94
+ */
95
+ export declare function canUseNodeFsGlob(options: FastGlobOptions | undefined): boolean;
76
96
  export declare function glob(patterns: Pattern | Pattern[], options?: FastGlobOptions): Promise<string[]>;
77
97
  /**
78
98
  * Create a stream of license file paths matching glob patterns.