korekt-cli 0.8.5 → 0.9.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korekt-cli",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "AI-powered code review CLI - Keep your kode korekt",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/git-logic.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execa } from 'execa';
2
2
  import chalk from 'chalk';
3
- import { detectCIProvider } from './utils.js';
3
+ import { detectCIProvider, getPrUrl } from './utils.js';
4
4
 
5
5
  /**
6
6
  * Truncate content to a maximum number of lines using "head and tail".
@@ -246,6 +246,7 @@ export async function runUncommittedReview(mode = 'unstaged') {
246
246
  source_branch: branchName,
247
247
  changed_lines: calculateChangedLines(changedFiles),
248
248
  is_ci: detectCIProvider() !== null,
249
+ pr_url: null, // Uncommitted changes are never part of a PR
249
250
  };
250
251
  } catch (error) {
251
252
  console.error(chalk.red('Failed to analyze uncommitted changes:'), error.message);
@@ -527,6 +528,7 @@ export async function runLocalReview(targetBranch = null, ignorePatterns = null)
527
528
  contributors,
528
529
  changed_lines: calculateChangedLines(changedFiles),
529
530
  is_ci: detectCIProvider() !== null,
531
+ pr_url: getPrUrl(),
530
532
  };
531
533
  } catch (error) {
532
534
  console.error(chalk.red('Failed to run local review analysis:'), error.message);
@@ -837,6 +837,7 @@ describe('is_ci flag in payload', () => {
837
837
  vi.mock('execa');
838
838
  vi.mock('./utils.js', () => ({
839
839
  detectCIProvider: vi.fn(),
840
+ getPrUrl: vi.fn().mockReturnValue(null),
840
841
  }));
841
842
  });
842
843
 
package/src/index.js CHANGED
@@ -17,7 +17,7 @@ import { formatReviewOutput } from './formatter.js';
17
17
  import { detectCIProvider, truncateFileData, formatErrorOutput } from './utils.js';
18
18
 
19
19
  // Re-export utilities for backward compatibility
20
- export { detectCIProvider, truncateFileData, formatErrorOutput } from './utils.js';
20
+ export { detectCIProvider, truncateFileData, formatErrorOutput, getPrUrl } from './utils.js';
21
21
 
22
22
  const require = createRequire(import.meta.url);
23
23
  const { version } = require('../package.json');
package/src/index.test.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { truncateFileData, formatErrorOutput, detectCIProvider } from './index.js';
2
+ import { truncateFileData, formatErrorOutput, detectCIProvider, getPrUrl } from './index.js';
3
3
 
4
4
  describe('CLI JSON output mode', () => {
5
5
  let stdoutSpy;
@@ -438,3 +438,111 @@ describe('--comment flag behavior', () => {
438
438
  expect(shouldShowConfirmation).toBe(true);
439
439
  });
440
440
  });
441
+
442
+ describe('getPrUrl', () => {
443
+ const originalEnv = process.env;
444
+
445
+ beforeEach(() => {
446
+ // Reset environment before each test
447
+ process.env = { ...originalEnv };
448
+ // Clear all PR-related env vars
449
+ delete process.env.GITHUB_REPOSITORY;
450
+ delete process.env.PR_NUMBER;
451
+ delete process.env.BITBUCKET_WORKSPACE;
452
+ delete process.env.BITBUCKET_REPO_SLUG;
453
+ delete process.env.BITBUCKET_PR_ID;
454
+ delete process.env.SYSTEM_COLLECTIONURI;
455
+ delete process.env.SYSTEM_TEAMPROJECT;
456
+ delete process.env.BUILD_REPOSITORY_NAME;
457
+ delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID;
458
+ });
459
+
460
+ afterEach(() => {
461
+ process.env = originalEnv;
462
+ });
463
+
464
+ it('should return GitHub PR URL when GitHub env vars are set', () => {
465
+ process.env.GITHUB_REPOSITORY = 'owner/repo';
466
+ process.env.PR_NUMBER = '123';
467
+
468
+ expect(getPrUrl()).toBe('https://github.com/owner/repo/pull/123');
469
+ });
470
+
471
+ it('should return Bitbucket PR URL when Bitbucket env vars are set', () => {
472
+ process.env.BITBUCKET_WORKSPACE = 'myworkspace';
473
+ process.env.BITBUCKET_REPO_SLUG = 'myrepo';
474
+ process.env.BITBUCKET_PR_ID = '456';
475
+
476
+ expect(getPrUrl()).toBe('https://bitbucket.org/myworkspace/myrepo/pull-requests/456');
477
+ });
478
+
479
+ it('should return Azure DevOps PR URL when Azure env vars are set', () => {
480
+ process.env.SYSTEM_COLLECTIONURI = 'https://dev.azure.com/myorg/';
481
+ process.env.SYSTEM_TEAMPROJECT = 'myproject';
482
+ process.env.BUILD_REPOSITORY_NAME = 'myrepo';
483
+ process.env.SYSTEM_PULLREQUEST_PULLREQUESTID = '789';
484
+
485
+ expect(getPrUrl()).toBe('https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequest/789');
486
+ });
487
+
488
+ it('should strip trailing slash from Azure DevOps collection URI', () => {
489
+ process.env.SYSTEM_COLLECTIONURI = 'https://dev.azure.com/myorg/';
490
+ process.env.SYSTEM_TEAMPROJECT = 'myproject';
491
+ process.env.BUILD_REPOSITORY_NAME = 'myrepo';
492
+ process.env.SYSTEM_PULLREQUEST_PULLREQUESTID = '789';
493
+
494
+ const url = getPrUrl();
495
+ expect(url).not.toContain('myorg//myproject');
496
+ expect(url).toContain('myorg/myproject');
497
+ });
498
+
499
+ it('should URL-encode spaces in Azure DevOps project and repo names', () => {
500
+ process.env.SYSTEM_COLLECTIONURI = 'https://dev.azure.com/myorg/';
501
+ process.env.SYSTEM_TEAMPROJECT = 'My Project';
502
+ process.env.BUILD_REPOSITORY_NAME = 'My Repo';
503
+ process.env.SYSTEM_PULLREQUEST_PULLREQUESTID = '789';
504
+
505
+ expect(getPrUrl()).toBe(
506
+ 'https://dev.azure.com/myorg/My%20Project/_git/My%20Repo/pullrequest/789'
507
+ );
508
+ });
509
+
510
+ it('should return null when no PR env vars are set', () => {
511
+ expect(getPrUrl()).toBe(null);
512
+ });
513
+
514
+ it('should return null when only partial GitHub env vars are set', () => {
515
+ process.env.GITHUB_REPOSITORY = 'owner/repo';
516
+ // PR_NUMBER not set
517
+
518
+ expect(getPrUrl()).toBe(null);
519
+ });
520
+
521
+ it('should return null when only partial Bitbucket env vars are set', () => {
522
+ process.env.BITBUCKET_WORKSPACE = 'myworkspace';
523
+ process.env.BITBUCKET_REPO_SLUG = 'myrepo';
524
+ // BITBUCKET_PR_ID not set
525
+
526
+ expect(getPrUrl()).toBe(null);
527
+ });
528
+
529
+ it('should return null when only partial Azure env vars are set', () => {
530
+ process.env.SYSTEM_COLLECTIONURI = 'https://dev.azure.com/myorg/';
531
+ process.env.SYSTEM_TEAMPROJECT = 'myproject';
532
+ // BUILD_REPOSITORY_NAME and SYSTEM_PULLREQUEST_PULLREQUESTID not set
533
+
534
+ expect(getPrUrl()).toBe(null);
535
+ });
536
+
537
+ it('should prioritize GitHub over other providers when multiple are set', () => {
538
+ // Set all providers
539
+ process.env.GITHUB_REPOSITORY = 'owner/repo';
540
+ process.env.PR_NUMBER = '123';
541
+ process.env.BITBUCKET_WORKSPACE = 'myworkspace';
542
+ process.env.BITBUCKET_REPO_SLUG = 'myrepo';
543
+ process.env.BITBUCKET_PR_ID = '456';
544
+
545
+ // GitHub should be detected first due to check order
546
+ expect(getPrUrl()).toBe('https://github.com/owner/repo/pull/123');
547
+ });
548
+ });
package/src/utils.js CHANGED
@@ -15,6 +15,36 @@ export function detectCIProvider() {
15
15
  return null;
16
16
  }
17
17
 
18
+ /**
19
+ * Build PR URL from CI environment variables
20
+ * @returns {string|null} Full PR URL or null if not in CI PR context
21
+ */
22
+ export function getPrUrl() {
23
+ // GitHub Actions
24
+ if (process.env.GITHUB_REPOSITORY && process.env.PR_NUMBER) {
25
+ return `https://github.com/${process.env.GITHUB_REPOSITORY}/pull/${process.env.PR_NUMBER}`;
26
+ }
27
+ // Bitbucket Pipelines
28
+ if (
29
+ process.env.BITBUCKET_WORKSPACE &&
30
+ process.env.BITBUCKET_REPO_SLUG &&
31
+ process.env.BITBUCKET_PR_ID
32
+ ) {
33
+ return `https://bitbucket.org/${process.env.BITBUCKET_WORKSPACE}/${process.env.BITBUCKET_REPO_SLUG}/pull-requests/${process.env.BITBUCKET_PR_ID}`;
34
+ }
35
+ // Azure DevOps Pipelines
36
+ if (
37
+ process.env.SYSTEM_COLLECTIONURI &&
38
+ process.env.SYSTEM_TEAMPROJECT &&
39
+ process.env.BUILD_REPOSITORY_NAME &&
40
+ process.env.SYSTEM_PULLREQUEST_PULLREQUESTID
41
+ ) {
42
+ const collectionUri = process.env.SYSTEM_COLLECTIONURI.replace(/\/$/, '');
43
+ return `${collectionUri}/${encodeURIComponent(process.env.SYSTEM_TEAMPROJECT)}/_git/${encodeURIComponent(process.env.BUILD_REPOSITORY_NAME)}/pullrequest/${process.env.SYSTEM_PULLREQUEST_PULLREQUESTID}`;
44
+ }
45
+ return null;
46
+ }
47
+
18
48
  /**
19
49
  * Truncates file data (diff and content) for display purposes
20
50
  * @param {Object} file - File object with path, status, diff, content, etc.