@socketsecurity/cli 1.1.29 → 1.1.31

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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.1.31](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.31) - 2025-11-19
8
+
9
+ ### Fixed
10
+ - Enhanced pull request descriptions to remove duplicate package listings for cleaner, more readable output
11
+
12
+ ## [1.1.30](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.30) - 2025-11-18
13
+
14
+ ### Changed
15
+ - Enhanced `SOCKET_CLI_COANA_LOCAL_PATH` to support compiled Coana CLI binaries alongside Node.js script files
16
+
17
+ ### Fixed
18
+ - Resolved PR creation workflow to properly recreate pull requests after closing or merging
19
+ - Corrected API token selection to honor `SOCKET_CLI_API_TOKEN` environment variable in package alert requests
20
+
7
21
  ## [1.1.29](https://github.com/SocketDev/socket-cli/releases/tag/v1.1.29) - 2025-11-16
8
22
 
9
23
  ### Added
package/dist/cli.js CHANGED
@@ -3264,7 +3264,76 @@ const cmdConfig = {
3264
3264
  }
3265
3265
  };
3266
3266
 
3267
+ /**
3268
+ * Branch cleanup utilities for socket fix command.
3269
+ * Manages local and remote branch lifecycle during PR creation.
3270
+ *
3271
+ * Critical distinction: Remote branches are sacred when a PR exists, disposable when they don't.
3272
+ */
3273
+
3274
+
3275
+ /**
3276
+ * Clean up a stale branch (both remote and local).
3277
+ * Safe to delete both since no PR exists for this branch.
3278
+ *
3279
+ * Returns true if cleanup succeeded or should continue, false if should skip GHSA.
3280
+ */
3281
+ async function cleanupStaleBranch(branch, ghsaId, cwd) {
3282
+ logger.logger.warn(`Stale branch ${branch} found without open PR, cleaning up...`);
3283
+ require$$9.debugFn('notice', `cleanup: deleting stale branch ${branch}`);
3284
+ const deleted = await utils.gitDeleteRemoteBranch(branch, cwd);
3285
+ if (!deleted) {
3286
+ logger.logger.error(`Failed to delete stale remote branch ${branch}, skipping ${ghsaId}.`);
3287
+ require$$9.debugFn('error', `cleanup: remote deletion failed for ${branch}`);
3288
+ return false;
3289
+ }
3290
+
3291
+ // Clean up local branch too to avoid conflicts.
3292
+ await utils.gitDeleteBranch(branch, cwd);
3293
+ return true;
3294
+ }
3295
+
3296
+ /**
3297
+ * Clean up branches after PR creation failure.
3298
+ * Safe to delete both remote and local since no PR was created.
3299
+ */
3300
+ async function cleanupFailedPrBranches(branch, cwd) {
3301
+ // Clean up pushed branch since PR creation failed.
3302
+ // Safe to delete both remote and local since no PR exists.
3303
+ await utils.gitDeleteRemoteBranch(branch, cwd);
3304
+ await utils.gitDeleteBranch(branch, cwd);
3305
+ }
3306
+
3307
+ /**
3308
+ * Clean up local branch after successful PR creation.
3309
+ * Keeps remote branch - PR needs it to be mergeable.
3310
+ */
3311
+ async function cleanupSuccessfulPrLocalBranch(branch, cwd) {
3312
+ // Clean up local branch only - keep remote branch for PR merge.
3313
+ await utils.gitDeleteBranch(branch, cwd);
3314
+ }
3315
+
3316
+ /**
3317
+ * Clean up branches in catch block after unexpected error.
3318
+ * Safe to delete both remote and local since no PR was created.
3319
+ */
3320
+ async function cleanupErrorBranches(branch, cwd, remoteBranchExists) {
3321
+ // Clean up remote branch if it exists (push may have succeeded before error).
3322
+ // Safe to delete both remote and local since no PR was created.
3323
+ if (remoteBranchExists) {
3324
+ await utils.gitDeleteRemoteBranch(branch, cwd);
3325
+ }
3326
+ await utils.gitDeleteBranch(branch, cwd);
3327
+ }
3328
+
3267
3329
  const GITHUB_ADVISORIES_URL = 'https://github.com/advisories';
3330
+
3331
+ /**
3332
+ * Extract unique package names with ecosystems from vulnerability details.
3333
+ */
3334
+ function getUniquePackages(details) {
3335
+ return [...new Set(details.vulnerabilities.nodes.map(v => `${v.package.name} (${v.package.ecosystem})`))];
3336
+ }
3268
3337
  function getSocketFixBranchName(ghsaId) {
3269
3338
  return `socket/fix/${ghsaId}`;
3270
3339
  }
@@ -3284,14 +3353,14 @@ function getSocketFixPullRequestBody(ghsaIds, ghsaDetails) {
3284
3353
  if (!details) {
3285
3354
  return body;
3286
3355
  }
3287
- const packages = details.vulnerabilities.nodes.map(v => `${v.package.name} (${v.package.ecosystem})`);
3356
+ const packages = getUniquePackages(details);
3288
3357
  return [body, '', '', `**Vulnerability Summary:** ${details.summary}`, '', `**Severity:** ${details.severity}`, '', `**Affected Packages:** ${arrays.joinAnd(packages)}`].join('\n');
3289
3358
  }
3290
3359
  return [`[Socket](${constants.default.SOCKET_WEBSITE_URL}) fixes for ${vulnCount} GHSAs.`, '', '**Fixed Vulnerabilities:**', ...ghsaIds.map(id => {
3291
3360
  const details = ghsaDetails?.get(id);
3292
3361
  const item = `- [${id}](${GITHUB_ADVISORIES_URL}/${id})`;
3293
3362
  if (details) {
3294
- const packages = details.vulnerabilities.nodes.map(v => `${v.package.name}`);
3363
+ const packages = getUniquePackages(details);
3295
3364
  return `${item} - ${details.summary} (${arrays.joinAnd(packages)})`;
3296
3365
  }
3297
3366
  return item;
@@ -3323,17 +3392,66 @@ async function openSocketFixPr(owner, repo, branch, ghsaIds, options) {
3323
3392
  require$$9.debugDir('inspect', {
3324
3393
  octokitPullsCreateParams
3325
3394
  });
3326
- return await octokit.pulls.create(octokitPullsCreateParams);
3395
+ const pr = await octokit.pulls.create(octokitPullsCreateParams);
3396
+ return {
3397
+ ok: true,
3398
+ pr
3399
+ };
3327
3400
  } catch (e) {
3328
- let message = `Failed to open pull request`;
3329
- const errors = e instanceof vendor.RequestError ? e.response?.data?.['errors'] : undefined;
3330
- if (Array.isArray(errors) && errors.length) {
3331
- const details = errors.map(d => `- ${d.message?.trim() ?? `${d.resource}.${d.field} (${d.code})`}`).join('\n');
3332
- message += `:\n${details}`;
3401
+ // Handle RequestError from Octokit.
3402
+ if (e instanceof vendor.RequestError) {
3403
+ const errors = e.response?.data?.['errors'];
3404
+ const errorMessages = Array.isArray(errors) ? errors.map(d => d.message?.trim() ?? `${d.resource}.${d.field} (${d.code})`) : [];
3405
+
3406
+ // Check for "PR already exists" error.
3407
+ if (errorMessages.some(msg => msg.toLowerCase().includes('pull request already exists'))) {
3408
+ require$$9.debugFn('error', 'Failed to open pull request: already exists');
3409
+ return {
3410
+ ok: false,
3411
+ reason: 'already_exists',
3412
+ error: e
3413
+ };
3414
+ }
3415
+
3416
+ // Check for validation errors (e.g., no commits between branches).
3417
+ if (errors && errors.length > 0) {
3418
+ const details = errorMessages.map(d => `- ${d}`).join('\n');
3419
+ require$$9.debugFn('error', `Failed to open pull request:\n${details}`);
3420
+ return {
3421
+ ok: false,
3422
+ reason: 'validation_error',
3423
+ error: e,
3424
+ details
3425
+ };
3426
+ }
3427
+
3428
+ // Check HTTP status codes.
3429
+ if (e.status === 403 || e.status === 401) {
3430
+ require$$9.debugFn('error', 'Failed to open pull request: permission denied');
3431
+ return {
3432
+ ok: false,
3433
+ reason: 'permission_denied',
3434
+ error: e
3435
+ };
3436
+ }
3437
+ if (e.status && e.status >= 500) {
3438
+ require$$9.debugFn('error', 'Failed to open pull request: network error');
3439
+ return {
3440
+ ok: false,
3441
+ reason: 'network_error',
3442
+ error: e
3443
+ };
3444
+ }
3333
3445
  }
3334
- require$$9.debugFn('error', message);
3446
+
3447
+ // Unknown error.
3448
+ require$$9.debugFn('error', `Failed to open pull request: ${e}`);
3449
+ return {
3450
+ ok: false,
3451
+ reason: 'unknown',
3452
+ error: e
3453
+ };
3335
3454
  }
3336
- return undefined;
3337
3455
  }
3338
3456
  async function getSocketFixPrs(owner, repo, options) {
3339
3457
  return (await getSocketFixPrsWithContext(owner, repo, options)).map(d => d.match);
@@ -3760,10 +3878,34 @@ async function coanaFix(fixConfig) {
3760
3878
  overallFixed = true;
3761
3879
  const branch = getSocketFixBranchName(ghsaId);
3762
3880
  try {
3763
- // Check if branch already exists.
3881
+ // Check if an open PR already exists for this GHSA.
3882
+ // eslint-disable-next-line no-await-in-loop
3883
+ const existingOpenPrs = await getSocketFixPrs(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, {
3884
+ ghsaId,
3885
+ states: constants.GQL_PR_STATE_OPEN
3886
+ });
3887
+ if (existingOpenPrs.length > 0) {
3888
+ const prNum = existingOpenPrs[0].number;
3889
+ logger.logger.info(`PR #${prNum} already exists for ${ghsaId}, skipping.`);
3890
+ require$$9.debugFn('notice', `skip: open PR #${prNum} exists for ${ghsaId}`);
3891
+ continue ghsaLoop;
3892
+ }
3893
+
3894
+ // If branch exists but no open PR, delete the stale branch.
3895
+ // This handles cases where PR creation failed but branch was pushed.
3764
3896
  // eslint-disable-next-line no-await-in-loop
3765
3897
  if (await utils.gitRemoteBranchExists(branch, cwd)) {
3766
- require$$9.debugFn('notice', `skip: remote branch "${branch}" exists`);
3898
+ // eslint-disable-next-line no-await-in-loop
3899
+ const shouldContinue = await cleanupStaleBranch(branch, ghsaId, cwd);
3900
+ if (!shouldContinue) {
3901
+ continue ghsaLoop;
3902
+ }
3903
+ }
3904
+
3905
+ // Check for GitHub token before doing any git operations.
3906
+ if (!fixEnv.githubToken) {
3907
+ logger.logger.error('Cannot create pull request: SOCKET_CLI_GITHUB_TOKEN environment variable is not set.\n' + 'Set SOCKET_CLI_GITHUB_TOKEN or GITHUB_TOKEN to enable PR creation.');
3908
+ require$$9.debugFn('error', `skip: missing GitHub token for ${ghsaId}`);
3767
3909
  continue ghsaLoop;
3768
3910
  }
3769
3911
  require$$9.debugFn('notice', `pr: creating for ${ghsaId}`);
@@ -3794,31 +3936,21 @@ async function coanaFix(fixConfig) {
3794
3936
  }
3795
3937
 
3796
3938
  // Set up git remote.
3797
- if (!fixEnv.githubToken) {
3798
- logger.logger.error('Cannot create pull request: SOCKET_CLI_GITHUB_TOKEN environment variable is not set.\n' + 'Set SOCKET_CLI_GITHUB_TOKEN or GITHUB_TOKEN to enable PR creation.');
3799
- // eslint-disable-next-line no-await-in-loop
3800
- await utils.gitResetAndClean(fixEnv.baseBranch, cwd);
3801
- // eslint-disable-next-line no-await-in-loop
3802
- await utils.gitCheckoutBranch(fixEnv.baseBranch, cwd);
3803
- // eslint-disable-next-line no-await-in-loop
3804
- await utils.gitDeleteBranch(branch, cwd);
3805
- continue ghsaLoop;
3806
- }
3807
3939
  // eslint-disable-next-line no-await-in-loop
3808
3940
  await utils.setGitRemoteGithubRepoUrl(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, fixEnv.githubToken, cwd);
3809
3941
 
3810
3942
  // eslint-disable-next-line no-await-in-loop
3811
- const prResponse = await openSocketFixPr(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, branch,
3943
+ const prResult = await openSocketFixPr(fixEnv.repoInfo.owner, fixEnv.repoInfo.repo, branch,
3812
3944
  // Single GHSA ID.
3813
3945
  [ghsaId], {
3814
3946
  baseBranch: fixEnv.baseBranch,
3815
3947
  cwd,
3816
3948
  ghsaDetails
3817
3949
  });
3818
- if (prResponse) {
3950
+ if (prResult.ok) {
3819
3951
  const {
3820
3952
  data
3821
- } = prResponse;
3953
+ } = prResult.pr;
3822
3954
  const prRef = `PR #${data.number}`;
3823
3955
  logger.logger.success(`Opened ${prRef} for ${ghsaId}.`);
3824
3956
  if (autopilot) {
@@ -3838,16 +3970,47 @@ async function coanaFix(fixConfig) {
3838
3970
  logger.logger.dedent();
3839
3971
  spinner?.dedent();
3840
3972
  }
3973
+
3974
+ // Clean up local branch only - keep remote branch for PR merge.
3975
+ // eslint-disable-next-line no-await-in-loop
3976
+ await cleanupSuccessfulPrLocalBranch(branch, cwd);
3977
+ } else {
3978
+ // Handle PR creation failures.
3979
+ if (prResult.reason === 'already_exists') {
3980
+ logger.logger.info(`PR already exists for ${ghsaId} (this should not happen due to earlier check).`);
3981
+ // Don't delete branch - PR exists and needs it.
3982
+ } else if (prResult.reason === 'validation_error') {
3983
+ logger.logger.error(`Failed to create PR for ${ghsaId}:\n${prResult.details}`);
3984
+ // eslint-disable-next-line no-await-in-loop
3985
+ await cleanupFailedPrBranches(branch, cwd);
3986
+ } else if (prResult.reason === 'permission_denied') {
3987
+ logger.logger.error(`Failed to create PR for ${ghsaId}: Permission denied. Check SOCKET_CLI_GITHUB_TOKEN permissions.`);
3988
+ // eslint-disable-next-line no-await-in-loop
3989
+ await cleanupFailedPrBranches(branch, cwd);
3990
+ } else if (prResult.reason === 'network_error') {
3991
+ logger.logger.error(`Failed to create PR for ${ghsaId}: Network error. Please try again.`);
3992
+ // eslint-disable-next-line no-await-in-loop
3993
+ await cleanupFailedPrBranches(branch, cwd);
3994
+ } else {
3995
+ logger.logger.error(`Failed to create PR for ${ghsaId}: ${prResult.error.message}`);
3996
+ // eslint-disable-next-line no-await-in-loop
3997
+ await cleanupFailedPrBranches(branch, cwd);
3998
+ }
3841
3999
  }
3842
4000
 
3843
4001
  // Reset back to base branch for next iteration.
3844
4002
  // eslint-disable-next-line no-await-in-loop
3845
- await utils.gitResetAndClean(branch, cwd);
4003
+ await utils.gitResetAndClean(fixEnv.baseBranch, cwd);
3846
4004
  // eslint-disable-next-line no-await-in-loop
3847
4005
  await utils.gitCheckoutBranch(fixEnv.baseBranch, cwd);
3848
4006
  } catch (e) {
3849
4007
  logger.logger.warn(`Unexpected condition: Push failed for ${ghsaId}, skipping PR creation.`);
3850
4008
  require$$9.debugDir('error', e);
4009
+ // Clean up branches (push may have succeeded before error).
4010
+ // eslint-disable-next-line no-await-in-loop
4011
+ const remoteBranchExists = await utils.gitRemoteBranchExists(branch, cwd);
4012
+ // eslint-disable-next-line no-await-in-loop
4013
+ await cleanupErrorBranches(branch, cwd, remoteBranchExists);
3851
4014
  // eslint-disable-next-line no-await-in-loop
3852
4015
  await utils.gitResetAndClean(fixEnv.baseBranch, cwd);
3853
4016
  // eslint-disable-next-line no-await-in-loop
@@ -15114,5 +15277,5 @@ void (async () => {
15114
15277
  await utils.captureException(e);
15115
15278
  }
15116
15279
  })();
15117
- //# debugId=af14f2c8-7f1a-4f12-bd1a-322165537e4f
15280
+ //# debugId=4f8753fd-5919-4ea2-a850-0b907ec7bd09
15118
15281
  //# sourceMappingURL=cli.js.map