@socketsecurity/cli-with-sentry 1.1.49 → 1.1.51

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 (35) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/bin/npm-cli.js +1 -1
  3. package/bin/npx-cli.js +2 -2
  4. package/dist/cli.js +100 -509
  5. package/dist/cli.js.map +1 -1
  6. package/dist/constants.js +6 -4
  7. package/dist/constants.js.map +1 -1
  8. package/dist/tsconfig.dts.tsbuildinfo +1 -1
  9. package/dist/types/commands/ci/handle-ci.d.mts.map +1 -1
  10. package/dist/types/commands/patch/cmd-patch.d.mts +1 -1
  11. package/dist/types/commands/patch/cmd-patch.d.mts.map +1 -1
  12. package/dist/types/commands/scan/cmd-scan-create.d.mts.map +1 -1
  13. package/dist/types/commands/scan/cmd-scan-reach.d.mts.map +1 -1
  14. package/dist/types/commands/scan/output-scan-report.d.mts.map +1 -1
  15. package/dist/types/commands/scan/perform-reachability-analysis.d.mts +1 -0
  16. package/dist/types/commands/scan/perform-reachability-analysis.d.mts.map +1 -1
  17. package/dist/types/commands/scan/reachability-flags.d.mts.map +1 -1
  18. package/dist/types/commands.d.mts +1 -1
  19. package/dist/types/constants.d.mts +1 -0
  20. package/dist/types/constants.d.mts.map +1 -1
  21. package/dist/types/utils/config.d.mts +6 -0
  22. package/dist/types/utils/config.d.mts.map +1 -1
  23. package/dist/types/utils/editable-json.d.mts +63 -0
  24. package/dist/types/utils/editable-json.d.mts.map +1 -0
  25. package/dist/types/utils/package-environment.d.mts.map +1 -1
  26. package/dist/utils.js +482 -50
  27. package/dist/utils.js.map +1 -1
  28. package/dist/vendor.js +13495 -3663
  29. package/package.json +4 -3
  30. package/dist/types/commands/patch/handle-patch.d.mts +0 -12
  31. package/dist/types/commands/patch/handle-patch.d.mts.map +0 -1
  32. package/dist/types/commands/patch/manifest-schema.d.mts +0 -34
  33. package/dist/types/commands/patch/manifest-schema.d.mts.map +0 -1
  34. package/dist/types/commands/patch/output-patch-result.d.mts +0 -5
  35. package/dist/types/commands/patch/output-patch-result.d.mts.map +0 -1
package/dist/utils.js CHANGED
@@ -16,9 +16,9 @@ var path = require('node:path');
16
16
  var regexps = require('../external/@socketsecurity/registry/lib/regexps');
17
17
  var prompts = require('../external/@socketsecurity/registry/lib/prompts');
18
18
  var spawn = require('../external/@socketsecurity/registry/lib/spawn');
19
- var fs = require('../external/@socketsecurity/registry/lib/fs');
19
+ var fs$1 = require('../external/@socketsecurity/registry/lib/fs');
20
20
  var require$$5 = require('node:module');
21
- var fs$1 = require('node:fs');
21
+ var fs = require('node:fs');
22
22
  var require$$13 = require('../external/@socketsecurity/registry/lib/url');
23
23
  var agent = require('../external/@socketsecurity/registry/lib/agent');
24
24
  var bin = require('../external/@socketsecurity/registry/lib/bin');
@@ -27,6 +27,7 @@ var require$$0$1 = require('node:url');
27
27
  var globs = require('../external/@socketsecurity/registry/lib/globs');
28
28
  var streams = require('../external/@socketsecurity/registry/lib/streams');
29
29
  var promises = require('node:timers/promises');
30
+ var require$$1 = require('node:util');
30
31
  var os = require('node:os');
31
32
  var process$1 = require('node:process');
32
33
  var require$$0 = require('node:crypto');
@@ -163,6 +164,324 @@ function debugGit(operation, success, details) {
163
164
  }
164
165
  }
165
166
 
167
+ /**
168
+ * @fileoverview EditableJson utility for non-destructive JSON file manipulation.
169
+ * Preserves formatting (indentation and line endings) when updating JSON files.
170
+ * This is a standalone implementation copied from @socketsecurity/lib/json/edit.
171
+ */
172
+
173
+
174
+ // Symbols used to store formatting metadata in JSON objects.
175
+ const INDENT_SYMBOL = Symbol.for('indent');
176
+ const NEWLINE_SYMBOL = Symbol.for('newline');
177
+
178
+ /**
179
+ * Formatting metadata for JSON files.
180
+ */
181
+
182
+ /**
183
+ * Options for saving editable JSON files.
184
+ */
185
+
186
+ /**
187
+ * Detect indentation from a JSON string.
188
+ * Supports space-based indentation (returns count) or mixed indentation (returns string).
189
+ */
190
+ function detectIndent(json) {
191
+ const match = json.match(/^[{[][\r\n]+(\s+)/m);
192
+ if (!match || !match[1]) {
193
+ return 2;
194
+ }
195
+ const indent = match[1];
196
+ if (/^ +$/.test(indent)) {
197
+ return indent.length;
198
+ }
199
+ return indent;
200
+ }
201
+
202
+ /**
203
+ * Detect newline character(s) from a JSON string.
204
+ * Supports LF (\n) and CRLF (\r\n) line endings.
205
+ */
206
+ function detectNewline(json) {
207
+ const match = json.match(/\r?\n/);
208
+ return match ? match[0] : '\n';
209
+ }
210
+
211
+ /**
212
+ * Sort object keys alphabetically.
213
+ * Creates a new object with sorted keys (does not mutate input).
214
+ */
215
+ function sortKeys(obj) {
216
+ const sorted = {
217
+ __proto__: null
218
+ };
219
+ const keys = Object.keys(obj).sort();
220
+ for (const key of keys) {
221
+ sorted[key] = obj[key];
222
+ }
223
+ return sorted;
224
+ }
225
+
226
+ /**
227
+ * Stringify JSON with specific formatting.
228
+ * Applies indentation and line ending preferences.
229
+ */
230
+ function stringifyWithFormatting(content, formatting) {
231
+ const {
232
+ indent,
233
+ newline
234
+ } = formatting;
235
+ const format = indent === undefined || indent === null ? ' ' : indent;
236
+ const eol = newline === undefined || newline === null ? '\n' : newline;
237
+ return `${JSON.stringify(content, undefined, format)}\n`.replace(/\n/g, eol);
238
+ }
239
+
240
+ /**
241
+ * Strip formatting symbols from content object.
242
+ * Removes Symbol.for('indent') and Symbol.for('newline') from the object.
243
+ */
244
+ function stripFormattingSymbols(content) {
245
+ const {
246
+ [INDENT_SYMBOL]: _indent,
247
+ [NEWLINE_SYMBOL]: _newline,
248
+ ...rest
249
+ } = content;
250
+ return rest;
251
+ }
252
+
253
+ /**
254
+ * Extract formatting from content object that has symbol-based metadata.
255
+ */
256
+ function getFormattingFromContent(content) {
257
+ const indent = content[INDENT_SYMBOL];
258
+ const newline = content[NEWLINE_SYMBOL];
259
+ return {
260
+ indent: indent === undefined || indent === null ? 2 : indent,
261
+ newline: newline === undefined || newline === null ? '\n' : newline
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Determine if content should be saved based on changes and options.
267
+ */
268
+ function shouldSave(currentContent, originalContent, originalFileContent, options = {}) {
269
+ const {
270
+ ignoreWhitespace = false,
271
+ sort = false
272
+ } = options;
273
+ const content = stripFormattingSymbols(currentContent);
274
+ const sortedContent = sort ? sortKeys(content) : content;
275
+ const origContent = originalContent ? stripFormattingSymbols(originalContent) : {};
276
+ if (ignoreWhitespace) {
277
+ return !require$$1.isDeepStrictEqual(sortedContent, origContent);
278
+ }
279
+ const formatting = getFormattingFromContent(currentContent);
280
+ const newFileContent = stringifyWithFormatting(sortedContent, formatting);
281
+ return newFileContent.trim() !== originalFileContent.trim();
282
+ }
283
+
284
+ /**
285
+ * Retry write operation with exponential backoff for file system issues.
286
+ */
287
+ async function retryWrite(filepath, content, retries = 3, baseDelay = 10) {
288
+ for (let attempt = 0; attempt <= retries; attempt++) {
289
+ try {
290
+ // eslint-disable-next-line no-await-in-loop
291
+ await fs.promises.writeFile(filepath, content);
292
+ if (process.platform === 'win32') {
293
+ // eslint-disable-next-line no-await-in-loop
294
+ await promises.setTimeout(50);
295
+ let accessRetries = 0;
296
+ const maxAccessRetries = 5;
297
+ while (accessRetries < maxAccessRetries) {
298
+ try {
299
+ // eslint-disable-next-line no-await-in-loop
300
+ await fs.promises.access(filepath);
301
+ // eslint-disable-next-line no-await-in-loop
302
+ await promises.setTimeout(10);
303
+ break;
304
+ } catch {
305
+ const delay = 20 * (accessRetries + 1);
306
+ // eslint-disable-next-line no-await-in-loop
307
+ await promises.setTimeout(delay);
308
+ accessRetries++;
309
+ }
310
+ }
311
+ }
312
+ return;
313
+ } catch (err) {
314
+ const isLastAttempt = attempt === retries;
315
+ const isRetriableError = err instanceof Error && 'code' in err && (err.code === 'EPERM' || err.code === 'EBUSY' || err.code === 'ENOENT');
316
+ if (!isRetriableError || isLastAttempt) {
317
+ throw err;
318
+ }
319
+ const delay = baseDelay * 2 ** attempt;
320
+ // eslint-disable-next-line no-await-in-loop
321
+ await promises.setTimeout(delay);
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Parse JSON string.
328
+ */
329
+ function parseJson(content) {
330
+ return JSON.parse(content);
331
+ }
332
+
333
+ /**
334
+ * Read file with retry logic for file system issues.
335
+ */
336
+ async function readFile(filepath) {
337
+ const maxRetries = process.platform === 'win32' ? 5 : 1;
338
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
339
+ try {
340
+ // eslint-disable-next-line no-await-in-loop
341
+ return await fs.promises.readFile(filepath, 'utf8');
342
+ } catch (err) {
343
+ const isLastAttempt = attempt === maxRetries;
344
+ const isEnoent = err instanceof Error && 'code' in err && err.code === 'ENOENT';
345
+ if (!isEnoent || isLastAttempt) {
346
+ throw err;
347
+ }
348
+ const delay = process.platform === 'win32' ? 50 * (attempt + 1) : 20;
349
+ // eslint-disable-next-line no-await-in-loop
350
+ await promises.setTimeout(delay);
351
+ }
352
+ }
353
+ throw new Error('Unreachable code');
354
+ }
355
+
356
+ /**
357
+ * EditableJson class for non-destructive JSON file manipulation.
358
+ * Preserves formatting when updating JSON files.
359
+ */
360
+ class EditableJson {
361
+ _canSave = true;
362
+ _content = {};
363
+ _path = undefined;
364
+ _readFileContent = '';
365
+ _readFileJson = undefined;
366
+ get content() {
367
+ return this._content;
368
+ }
369
+ get filename() {
370
+ const path = this._path;
371
+ if (!path) {
372
+ return '';
373
+ }
374
+ return path;
375
+ }
376
+ get path() {
377
+ return this._path;
378
+ }
379
+
380
+ /**
381
+ * Create a new JSON file instance.
382
+ */
383
+ create(path) {
384
+ this._path = path;
385
+ this._content = {};
386
+ this._canSave = true;
387
+ return this;
388
+ }
389
+
390
+ /**
391
+ * Initialize from content object (disables saving).
392
+ */
393
+ fromContent(data) {
394
+ this._content = data;
395
+ this._canSave = false;
396
+ return this;
397
+ }
398
+
399
+ /**
400
+ * Initialize from JSON string.
401
+ */
402
+ fromJSON(data) {
403
+ const parsed = parseJson(data);
404
+ const indent = detectIndent(data);
405
+ const newline = detectNewline(data)
406
+ // Use type assertion to allow symbol indexing.
407
+ ;
408
+ parsed[INDENT_SYMBOL] = indent;
409
+ parsed[NEWLINE_SYMBOL] = newline;
410
+ this._content = parsed;
411
+ return this;
412
+ }
413
+
414
+ /**
415
+ * Load JSON file from disk.
416
+ */
417
+ async load(path, create) {
418
+ this._path = path;
419
+ try {
420
+ this._readFileContent = await readFile(this.filename);
421
+ this.fromJSON(this._readFileContent);
422
+ this._readFileJson = parseJson(this._readFileContent);
423
+ } catch (err) {
424
+ if (!create) {
425
+ throw err;
426
+ }
427
+ // File doesn't exist and create is true - initialize empty.
428
+ this._content = {};
429
+ this._readFileContent = '';
430
+ this._readFileJson = undefined;
431
+ this._canSave = true;
432
+ }
433
+ return this;
434
+ }
435
+
436
+ /**
437
+ * Update content with new values.
438
+ */
439
+ update(content) {
440
+ this._content = {
441
+ ...this._content,
442
+ ...content
443
+ };
444
+ return this;
445
+ }
446
+
447
+ /**
448
+ * Save JSON file to disk asynchronously.
449
+ */
450
+ async save(options) {
451
+ if (!this._canSave || this.content === undefined) {
452
+ throw new Error('No file path to save to');
453
+ }
454
+ if (!shouldSave(this._content, this._readFileJson, this._readFileContent, options)) {
455
+ return false;
456
+ }
457
+ const content = stripFormattingSymbols(this._content);
458
+ const sortedContent = options?.sort ? sortKeys(content) : content;
459
+ const formatting = getFormattingFromContent(this._content);
460
+ const fileContent = stringifyWithFormatting(sortedContent, formatting);
461
+ await retryWrite(this.filename, fileContent);
462
+ this._readFileContent = fileContent;
463
+ this._readFileJson = parseJson(fileContent);
464
+ return true;
465
+ }
466
+
467
+ /**
468
+ * Check if save will occur based on current changes.
469
+ */
470
+ willSave(options) {
471
+ if (!this._canSave || this.content === undefined) {
472
+ return false;
473
+ }
474
+ return shouldSave(this._content, this._readFileJson, this._readFileContent, options);
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Get the EditableJson class for JSON file manipulation.
480
+ */
481
+ function getEditableJsonClass() {
482
+ return EditableJson;
483
+ }
484
+
166
485
  /**
167
486
  * Error utilities for Socket CLI.
168
487
  * Provides consistent error handling, formatting, and message extraction.
@@ -307,14 +626,15 @@ function getConfigValues() {
307
626
  socketAppDataPath
308
627
  } = constants.default;
309
628
  if (socketAppDataPath) {
310
- const raw = fs.safeReadFileSync(socketAppDataPath);
629
+ const configFilePath = path.join(socketAppDataPath, 'config.json');
630
+ const raw = fs$1.safeReadFileSync(configFilePath);
311
631
  if (raw) {
312
632
  try {
313
633
  Object.assign(_cachedConfig, JSON.parse(Buffer.from(raw, 'base64').toString()));
314
- debugConfig(socketAppDataPath, true);
634
+ debugConfig(configFilePath, true);
315
635
  } catch (e) {
316
- logger.logger.warn(`Failed to parse config at ${socketAppDataPath}`);
317
- debugConfig(socketAppDataPath, false, e);
636
+ logger.logger.warn(`Failed to parse config at ${configFilePath}`);
637
+ debugConfig(configFilePath, false, e);
318
638
  }
319
639
  // Normalize apiKey to apiToken and persist it.
320
640
  // This is a one time migration per user.
@@ -324,7 +644,7 @@ function getConfigValues() {
324
644
  updateConfigValue(constants.CONFIG_KEY_API_TOKEN, token);
325
645
  }
326
646
  } else {
327
- fs$1.mkdirSync(path.dirname(socketAppDataPath), {
647
+ fs.mkdirSync(socketAppDataPath, {
328
648
  recursive: true
329
649
  });
330
650
  }
@@ -353,10 +673,10 @@ function findSocketYmlSync(dir = process.cwd()) {
353
673
  let prevDir = null;
354
674
  while (dir !== prevDir) {
355
675
  let ymlPath = path.join(dir, constants.SOCKET_YML);
356
- let yml = fs.safeReadFileSync(ymlPath);
676
+ let yml = fs$1.safeReadFileSync(ymlPath);
357
677
  if (yml === undefined) {
358
678
  ymlPath = path.join(dir, constants.SOCKET_YAML);
359
- yml = fs.safeReadFileSync(ymlPath);
679
+ yml = fs$1.safeReadFileSync(ymlPath);
360
680
  }
361
681
  if (typeof yml === 'string') {
362
682
  try {
@@ -519,11 +839,68 @@ function updateConfigValue(configKey, value) {
519
839
  _pendingSave = true;
520
840
  process.nextTick(() => {
521
841
  _pendingSave = false;
842
+ // Capture the config state at write time, not at schedule time.
843
+ // This ensures all updates in the same tick are included.
844
+ const configToSave = {
845
+ ...localConfig
846
+ };
522
847
  const {
523
848
  socketAppDataPath
524
849
  } = constants.default;
525
850
  if (socketAppDataPath) {
526
- fs$1.writeFileSync(socketAppDataPath, Buffer.from(JSON.stringify(localConfig)).toString('base64'));
851
+ fs.mkdirSync(socketAppDataPath, {
852
+ recursive: true
853
+ });
854
+ const configFilePath = path.join(socketAppDataPath, 'config.json');
855
+ // Read existing file to preserve formatting, then update with new values.
856
+ const existingRaw = fs$1.safeReadFileSync(configFilePath);
857
+ const EditableJson = getEditableJsonClass();
858
+ const editor = new EditableJson();
859
+ if (existingRaw !== undefined) {
860
+ const rawString = Buffer.isBuffer(existingRaw) ? existingRaw.toString('utf8') : existingRaw;
861
+ try {
862
+ const decoded = Buffer.from(rawString, 'base64').toString('utf8');
863
+ editor.fromJSON(decoded);
864
+ } catch {
865
+ // If decoding fails, start fresh.
866
+ }
867
+ } else {
868
+ // Initialize empty editor for new file.
869
+ editor.create(configFilePath);
870
+ }
871
+ // Update with the captured config state.
872
+ // Note: We need to handle deletions explicitly since editor.update() only merges.
873
+ // First, get all keys from the existing content.
874
+ const existingKeys = new Set(Object.keys(editor.content).filter(k => typeof k === 'string'));
875
+ const newKeys = new Set(Object.keys(configToSave));
876
+
877
+ // Delete keys that are in existing but not in new config.
878
+ for (const key of existingKeys) {
879
+ if (!newKeys.has(key)) {
880
+ delete editor.content[key];
881
+ }
882
+ }
883
+
884
+ // Now update with new values.
885
+ editor.update(configToSave);
886
+ // Use the editor's internal stringify which preserves formatting.
887
+ // Extract the formatting symbols from the content.
888
+ const INDENT_SYMBOL = Symbol.for('indent');
889
+ const NEWLINE_SYMBOL = Symbol.for('newline');
890
+ const indent = editor.content[INDENT_SYMBOL] ?? 2;
891
+ const newline = editor.content[NEWLINE_SYMBOL] ?? '\n';
892
+
893
+ // Strip formatting symbols from content.
894
+ const contentToSave = {};
895
+ for (const [key, val] of Object.entries(editor.content)) {
896
+ if (typeof key === 'string') {
897
+ contentToSave[key] = val;
898
+ }
899
+ }
900
+
901
+ // Stringify with formatting preserved.
902
+ const jsonContent = JSON.stringify(contentToSave, undefined, indent).replace(/\n/g, newline);
903
+ fs.writeFileSync(configFilePath, Buffer.from(jsonContent + newline).toString('base64'));
527
904
  }
528
905
  });
529
906
  }
@@ -3894,7 +4271,7 @@ function* walkNestedMap(map, keys = []) {
3894
4271
  */
3895
4272
 
3896
4273
  function extractTier1ReachabilityScanId(socketFactsFile) {
3897
- const json = fs.readJsonSync(socketFactsFile, {
4274
+ const json = fs$1.readJsonSync(socketFactsFile, {
3898
4275
  throws: false
3899
4276
  });
3900
4277
  const tier1ReachabilityScanId = String(json?.['tier1ReachabilityScanId'] ?? '').trim();
@@ -3952,7 +4329,7 @@ async function findUp(name, options) {
3952
4329
  const thePath = path.join(dir, name);
3953
4330
  try {
3954
4331
  // eslint-disable-next-line no-await-in-loop
3955
- const stats = await fs$1.promises.stat(thePath);
4332
+ const stats = await fs.promises.stat(thePath);
3956
4333
  if (!onlyDirectories && stats.isFile()) {
3957
4334
  return thePath;
3958
4335
  }
@@ -3994,7 +4371,7 @@ async function getWorkspaceGlobs(agent, cwd = process.cwd()) {
3994
4371
  let workspacePatterns;
3995
4372
  if (agent === constants.PNPM) {
3996
4373
  const workspacePath = path.join(cwd, 'pnpm-workspace.yaml');
3997
- const yml = await fs.safeReadFile(workspacePath);
4374
+ const yml = await fs$1.safeReadFile(workspacePath);
3998
4375
  if (yml) {
3999
4376
  try {
4000
4377
  workspacePatterns = vendor.distExports$1.parse(yml)?.packages;
@@ -4104,7 +4481,7 @@ async function globWithGitIgnore(patterns, options) {
4104
4481
  cwd,
4105
4482
  ignore: DEFAULT_IGNORE_FOR_GIT_IGNORE
4106
4483
  });
4107
- for await (const ignorePatterns of streams.transform(gitIgnoreStream, async filepath => ignoreFileToGlobPatterns((await fs.safeReadFile(filepath)) ?? '', filepath, cwd), {
4484
+ for await (const ignorePatterns of streams.transform(gitIgnoreStream, async filepath => ignoreFileToGlobPatterns((await fs$1.safeReadFile(filepath)) ?? '', filepath, cwd), {
4108
4485
  concurrency: 8
4109
4486
  })) {
4110
4487
  for (const p of ignorePatterns) {
@@ -4166,7 +4543,7 @@ function pathsToGlobPatterns(paths, cwd) {
4166
4543
  }
4167
4544
  const absolutePath = path.isAbsolute(p) ? p : path.resolve(cwd ?? process.cwd(), p);
4168
4545
  // If the path is a directory, scan it recursively for all files.
4169
- if (fs.isDirSync(absolutePath)) {
4546
+ if (fs$1.isDirSync(absolutePath)) {
4170
4547
  return `${p}/**/*`;
4171
4548
  }
4172
4549
  return p;
@@ -4219,11 +4596,11 @@ function findNpmDirPathSync(npmBinPath) {
4219
4596
  // Use existsSync here because statsSync, even with { throwIfNoEntry: false },
4220
4597
  // will throw an ENOTDIR error for paths like ./a-file-that-exists/a-directory-that-does-not.
4221
4598
  // See https://github.com/nodejs/node/issues/56993.
4222
- fs.isDirSync(libNmNpmPath)) {
4599
+ fs$1.isDirSync(libNmNpmPath)) {
4223
4600
  thePath = libNmNpmPath;
4224
4601
  }
4225
- const hasNmInCurrPath = fs.isDirSync(path.join(thePath, constants.NODE_MODULES));
4226
- const hasNmInParentPath = !hasNmInCurrPath && fs.isDirSync(path.join(thePath, `../${constants.NODE_MODULES}`));
4602
+ const hasNmInCurrPath = fs$1.isDirSync(path.join(thePath, constants.NODE_MODULES));
4603
+ const hasNmInParentPath = !hasNmInCurrPath && fs$1.isDirSync(path.join(thePath, `../${constants.NODE_MODULES}`));
4227
4604
  if (
4228
4605
  // npm bin paths may look like:
4229
4606
  // /usr/local/share/npm/bin/npm
@@ -4241,7 +4618,7 @@ function findNpmDirPathSync(npmBinPath) {
4241
4618
  // Optimistically look for the default location.
4242
4619
  path.basename(thePath) === constants.NPM ||
4243
4620
  // Chocolatey installs npm bins in the same directory as node bins.
4244
- WIN32 && fs$1.existsSync(path.join(thePath, `${constants.NPM}.cmd`)))) {
4621
+ WIN32 && fs.existsSync(path.join(thePath, `${constants.NPM}.cmd`)))) {
4245
4622
  return hasNmInParentPath ? path.dirname(thePath) : thePath;
4246
4623
  }
4247
4624
  const parent = path.dirname(thePath);
@@ -4635,7 +5012,7 @@ function getDefaultSocketJson() {
4635
5012
  }
4636
5013
  function readSocketJsonSync(cwd, defaultOnError = false) {
4637
5014
  const sockJsonPath = path.join(cwd, constants.SOCKET_JSON);
4638
- if (!fs$1.existsSync(sockJsonPath)) {
5015
+ if (!fs.existsSync(sockJsonPath)) {
4639
5016
  require$$9.debugFn('notice', `miss: ${constants.SOCKET_JSON} not found at ${cwd}`);
4640
5017
  return {
4641
5018
  ok: true,
@@ -4644,7 +5021,7 @@ function readSocketJsonSync(cwd, defaultOnError = false) {
4644
5021
  }
4645
5022
  let jsonContent = null;
4646
5023
  try {
4647
- jsonContent = fs$1.readFileSync(sockJsonPath, 'utf8');
5024
+ jsonContent = fs.readFileSync(sockJsonPath, 'utf8');
4648
5025
  } catch (e) {
4649
5026
  if (defaultOnError) {
4650
5027
  logger.logger.warn(`Failed to read ${constants.SOCKET_JSON}, using default`);
@@ -4718,7 +5095,7 @@ async function writeSocketJson(cwd, sockJson) {
4718
5095
  };
4719
5096
  }
4720
5097
  const filepath = path.join(cwd, constants.SOCKET_JSON);
4721
- await fs$1.promises.writeFile(filepath, `${jsonContent}\n`, 'utf8');
5098
+ await fs.promises.writeFile(filepath, `${jsonContent}\n`, 'utf8');
4722
5099
  return {
4723
5100
  ok: true,
4724
5101
  data: undefined
@@ -4755,11 +5132,11 @@ async function readCache(key,
4755
5132
  // 5 minute in milliseconds time to live (TTL).
4756
5133
  ttlMs = 5 * 60 * 1000) {
4757
5134
  const cacheJsonPath = path.join(constants.default.githubCachePath, `${key}.json`);
4758
- const stat = fs.safeStatsSync(cacheJsonPath);
5135
+ const stat = fs$1.safeStatsSync(cacheJsonPath);
4759
5136
  if (stat) {
4760
5137
  const isExpired = Date.now() - stat.mtimeMs > ttlMs;
4761
5138
  if (!isExpired) {
4762
- return await fs.readJson(cacheJsonPath);
5139
+ return await fs$1.readJson(cacheJsonPath);
4763
5140
  }
4764
5141
  }
4765
5142
  return undefined;
@@ -4769,12 +5146,12 @@ async function writeCache(key, data) {
4769
5146
  githubCachePath
4770
5147
  } = constants.default;
4771
5148
  const cacheJsonPath = path.join(githubCachePath, `${key}.json`);
4772
- if (!fs$1.existsSync(githubCachePath)) {
4773
- await fs$1.promises.mkdir(githubCachePath, {
5149
+ if (!fs.existsSync(githubCachePath)) {
5150
+ await fs.promises.mkdir(githubCachePath, {
4774
5151
  recursive: true
4775
5152
  });
4776
5153
  }
4777
- await fs.writeJson(cacheJsonPath, data);
5154
+ await fs$1.writeJson(cacheJsonPath, data);
4778
5155
  }
4779
5156
  async function cacheFetch(key, fetcher, ttlMs) {
4780
5157
  // Optionally disable cache.
@@ -5251,7 +5628,7 @@ const COMPLETION_CMD_PREFIX = 'complete -F _socket_completion';
5251
5628
  function getCompletionSourcingCommand() {
5252
5629
  // Note: this is exported to distPath in .config/rollup.dist.config.mjs
5253
5630
  const completionScriptExportPath = path.join(constants.default.distPath, 'socket-completion.bash');
5254
- if (!fs$1.existsSync(completionScriptExportPath)) {
5631
+ if (!fs.existsSync(completionScriptExportPath)) {
5255
5632
  return {
5256
5633
  ok: false,
5257
5634
  message: 'Tab Completion script not found',
@@ -5372,7 +5749,7 @@ function getNpmRequire() {
5372
5749
  if (_npmRequire === undefined) {
5373
5750
  const npmDirPath = getNpmDirPath();
5374
5751
  const npmNmPath = path.join(npmDirPath, `${constants.NODE_MODULES}/npm`);
5375
- _npmRequire = require$$5.createRequire(path.join(fs$1.existsSync(npmNmPath) ? npmNmPath : npmDirPath, '<dummy-basename>'));
5752
+ _npmRequire = require$$5.createRequire(path.join(fs.existsSync(npmNmPath) ? npmNmPath : npmDirPath, '<dummy-basename>'));
5376
5753
  }
5377
5754
  return _npmRequire;
5378
5755
  }
@@ -5588,8 +5965,8 @@ const readLockFileByAgent = (() => {
5588
5965
  return undefined;
5589
5966
  };
5590
5967
  }
5591
- const binaryReader = wrapReader(fs.readFileBinary);
5592
- const defaultReader = wrapReader(async lockPath => await fs.readFileUtf8(lockPath));
5968
+ const binaryReader = wrapReader(fs$1.readFileBinary);
5969
+ const defaultReader = wrapReader(async lockPath => await fs$1.readFileUtf8(lockPath));
5593
5970
  return new Map([[BUN, wrapReader(async (lockPath, agentExecPath, cwd = process.cwd()) => {
5594
5971
  const ext = path.extname(lockPath);
5595
5972
  if (ext === EXT_LOCK) {
@@ -5637,20 +6014,54 @@ const LOCKS = {
5637
6014
  // it has to be handled differently.
5638
6015
  [`${NODE_MODULES}/${DOT_PACKAGE_LOCK_JSON}`]: NPM
5639
6016
  };
6017
+ function preferWindowsCmdShim(binPath, binName) {
6018
+ // Only Windows uses .cmd shims
6019
+ if (!constants.default.WIN32) {
6020
+ return binPath;
6021
+ }
6022
+
6023
+ // Relative paths might be shell commands or aliases, not file paths with potential shims
6024
+ if (!path.isAbsolute(binPath)) {
6025
+ return binPath;
6026
+ }
6027
+
6028
+ // If the path already has an extension (.exe, .bat, etc.), it is probably a Windows executable
6029
+ if (path.extname(binPath) !== '') {
6030
+ return binPath;
6031
+ }
6032
+
6033
+ // Ensures binPath actually points to the expected binary, not a parent directory that happens to match `binName`
6034
+ // For example, if binPath is C:\foo\npm\something and binName is npm, we shouldn't replace it
6035
+ if (path.basename(binPath).toLowerCase() !== binName.toLowerCase()) {
6036
+ return binPath;
6037
+ }
6038
+
6039
+ // Finally attempt to construct a .cmd shim from binPAth
6040
+ const cmdShim = path.join(path.dirname(binPath), `${binName}.cmd`);
6041
+
6042
+ // Ensure shim exists, otherwise failback to binPath
6043
+ return fs.existsSync(cmdShim) ? cmdShim : binPath;
6044
+ }
5640
6045
  async function getAgentExecPath(agent) {
5641
6046
  const binName = binByAgent.get(agent);
5642
6047
  if (binName === NPM) {
5643
6048
  // Try to use constants.npmExecPath first, but verify it exists.
5644
- const npmPath = constants.default.npmExecPath;
5645
- if (fs$1.existsSync(npmPath)) {
6049
+ const npmPath = preferWindowsCmdShim(constants.default.npmExecPath, NPM);
6050
+ if (fs.existsSync(npmPath)) {
5646
6051
  return npmPath;
5647
6052
  }
5648
6053
  // If npmExecPath doesn't exist, try common locations.
5649
6054
  // Check npm in the same directory as node.
5650
6055
  const nodeDir = path.dirname(process.execPath);
6056
+ if (constants.default.WIN32) {
6057
+ const npmCmdInNodeDir = path.join(nodeDir, `${NPM}.cmd`);
6058
+ if (fs.existsSync(npmCmdInNodeDir)) {
6059
+ return npmCmdInNodeDir;
6060
+ }
6061
+ }
5651
6062
  const npmInNodeDir = path.join(nodeDir, NPM);
5652
- if (fs$1.existsSync(npmInNodeDir)) {
5653
- return npmInNodeDir;
6063
+ if (fs.existsSync(npmInNodeDir)) {
6064
+ return preferWindowsCmdShim(npmInNodeDir, NPM);
5654
6065
  }
5655
6066
  // Fall back to whichBin.
5656
6067
  return (await bin.whichBin(binName, {
@@ -5660,7 +6071,7 @@ async function getAgentExecPath(agent) {
5660
6071
  if (binName === PNPM) {
5661
6072
  // Try to use constants.pnpmExecPath first, but verify it exists.
5662
6073
  const pnpmPath = constants.default.pnpmExecPath;
5663
- if (fs$1.existsSync(pnpmPath)) {
6074
+ if (fs.existsSync(pnpmPath)) {
5664
6075
  return pnpmPath;
5665
6076
  }
5666
6077
  // Fall back to whichBin.
@@ -5677,19 +6088,42 @@ async function getAgentVersion(agent, agentExecPath, cwd) {
5677
6088
  const quotedCmd = `\`${agent} ${constants.FLAG_VERSION}\``;
5678
6089
  require$$9.debugFn('stdio', `spawn: ${quotedCmd}`);
5679
6090
  try {
6091
+ let stdout;
6092
+
6093
+ // Some package manager "executables" may resolve to non-executable wrapper scripts
6094
+ // (e.g. the extensionless `npm` shim on Windows). Resolve the underlying entrypoint
6095
+ // and run it with Node when it is a JS file.
6096
+ let shouldRunWithNode = null;
6097
+ if (constants.default.WIN32) {
6098
+ try {
6099
+ const resolved = bin.resolveBinPathSync(agentExecPath);
6100
+ const ext = path.extname(resolved).toLowerCase();
6101
+ if (ext === '.js' || ext === '.cjs' || ext === '.mjs') {
6102
+ shouldRunWithNode = resolved;
6103
+ }
6104
+ } catch (e) {
6105
+ require$$9.debugFn('warn', `Failed to resolve bin path for ${agentExecPath}, falling back to direct spawn.`);
6106
+ require$$9.debugDir('error', e);
6107
+ }
6108
+ }
6109
+ if (shouldRunWithNode) {
6110
+ stdout = (await spawn.spawn(constants.default.execPath, [...constants.default.nodeNoWarningsFlags, shouldRunWithNode, constants.FLAG_VERSION], {
6111
+ cwd
6112
+ })).stdout;
6113
+ } else {
6114
+ stdout = (await spawn.spawn(agentExecPath, [constants.FLAG_VERSION], {
6115
+ cwd,
6116
+ // On Windows, package managers are often .cmd files that require shell execution.
6117
+ // The spawn function from @socketsecurity/registry will handle this properly
6118
+ // when shell is true.
6119
+ shell: constants.default.WIN32
6120
+ })).stdout;
6121
+ }
5680
6122
  result =
5681
6123
  // Coerce version output into a valid semver version by passing it through
5682
6124
  // semver.coerce which strips leading v's, carets (^), comparators (<,<=,>,>=,=),
5683
6125
  // and tildes (~).
5684
- vendor.semverExports.coerce(
5685
- // All package managers support the "--version" flag.
5686
- (await spawn.spawn(agentExecPath, [constants.FLAG_VERSION], {
5687
- cwd,
5688
- // On Windows, package managers are often .cmd files that require shell execution.
5689
- // The spawn function from @socketsecurity/registry will handle this properly
5690
- // when shell is true.
5691
- shell: constants.default.WIN32
5692
- })).stdout) ?? undefined;
6126
+ vendor.semverExports.coerce(stdout) ?? undefined;
5693
6127
  } catch (e) {
5694
6128
  require$$9.debugFn('error', `Package manager command failed: ${quotedCmd}`);
5695
6129
  require$$9.debugDir('inspect', {
@@ -5711,7 +6145,7 @@ async function detectPackageEnvironment({
5711
6145
  const pkgJsonPath = lockPath ? path.resolve(lockPath, `${isHiddenLockFile ? '../' : ''}../${PACKAGE_JSON}`) : await findUp(PACKAGE_JSON, {
5712
6146
  cwd
5713
6147
  });
5714
- const pkgPath = pkgJsonPath && fs$1.existsSync(pkgJsonPath) ? path.dirname(pkgJsonPath) : undefined;
6148
+ const pkgPath = pkgJsonPath && fs.existsSync(pkgJsonPath) ? path.dirname(pkgJsonPath) : undefined;
5715
6149
  const editablePkgJson = pkgPath ? await packages.readPackageJson(pkgPath, {
5716
6150
  editable: true
5717
6151
  }) : undefined;
@@ -6266,7 +6700,7 @@ function parsePnpmLockfile(lockfileContent) {
6266
6700
  return require$$11.isObjectObject(result) ? result : null;
6267
6701
  }
6268
6702
  async function readPnpmLockfile(lockfilePath) {
6269
- return fs$1.existsSync(lockfilePath) ? await fs.readFileUtf8(lockfilePath) : undefined;
6703
+ return fs.existsSync(lockfilePath) ? await fs$1.readFileUtf8(lockfilePath) : undefined;
6270
6704
  }
6271
6705
  function stripLeadingPnpmDepPathSlash(depPath) {
6272
6706
  return isPnpmDepPath(depPath) ? depPath.slice(1) : depPath;
@@ -7041,7 +7475,6 @@ exports.getOrgSlugs = getOrgSlugs;
7041
7475
  exports.getOutputKind = getOutputKind;
7042
7476
  exports.getPackageFilesForScan = getPackageFilesForScan;
7043
7477
  exports.getPublicApiToken = getPublicApiToken;
7044
- exports.getPurlObject = getPurlObject;
7045
7478
  exports.getRepoInfo = getRepoInfo;
7046
7479
  exports.getRepoName = getRepoName;
7047
7480
  exports.getSocketDevPackageOverviewUrlFromPurl = getSocketDevPackageOverviewUrlFromPurl;
@@ -7085,7 +7518,6 @@ exports.mdTableStringNumber = mdTableStringNumber;
7085
7518
  exports.meowOrExit = meowOrExit;
7086
7519
  exports.meowWithSubcommands = meowWithSubcommands;
7087
7520
  exports.msAtHome = msAtHome;
7088
- exports.normalizePurl = normalizePurl;
7089
7521
  exports.parsePnpmLockfile = parsePnpmLockfile;
7090
7522
  exports.queryApiSafeJson = queryApiSafeJson;
7091
7523
  exports.queryApiSafeText = queryApiSafeText;
@@ -7120,5 +7552,5 @@ exports.updateConfigValue = updateConfigValue;
7120
7552
  exports.walkNestedMap = walkNestedMap;
7121
7553
  exports.webLink = webLink;
7122
7554
  exports.writeSocketJson = writeSocketJson;
7123
- //# debugId=a083299b-d999-403d-b54b-00740d629c69
7555
+ //# debugId=223b2750-92b4-4170-901e-7e9746f5b2c5
7124
7556
  //# sourceMappingURL=utils.js.map