@bragduck/cli 1.0.3 → 2.0.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.
- package/README.md +129 -33
- package/dist/bin/bragduck.js +315 -79
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +63 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/bin/bragduck.js
CHANGED
|
@@ -32,12 +32,10 @@ var init_constants = __esm({
|
|
|
32
32
|
config({ path: join(__dirname, "..", "..", ".env") });
|
|
33
33
|
APP_NAME = "bragduck";
|
|
34
34
|
CONFIG_KEYS = {
|
|
35
|
-
API_BASE_URL: "apiBaseUrl",
|
|
36
35
|
DEFAULT_COMMIT_DAYS: "defaultCommitDays",
|
|
37
36
|
AUTO_VERSION_CHECK: "autoVersionCheck"
|
|
38
37
|
};
|
|
39
38
|
DEFAULT_CONFIG = {
|
|
40
|
-
apiBaseUrl: process.env.API_BASE_URL || "https://api.bragduck.com",
|
|
41
39
|
defaultCommitDays: 30,
|
|
42
40
|
autoVersionCheck: true
|
|
43
41
|
};
|
|
@@ -308,7 +306,7 @@ var init_storage_service = __esm({
|
|
|
308
306
|
});
|
|
309
307
|
|
|
310
308
|
// src/utils/errors.ts
|
|
311
|
-
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError;
|
|
309
|
+
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError;
|
|
312
310
|
var init_errors = __esm({
|
|
313
311
|
"src/utils/errors.ts"() {
|
|
314
312
|
"use strict";
|
|
@@ -366,6 +364,12 @@ var init_errors = __esm({
|
|
|
366
364
|
this.code = "TOKEN_EXPIRED";
|
|
367
365
|
}
|
|
368
366
|
};
|
|
367
|
+
GitHubError = class extends BragduckError {
|
|
368
|
+
constructor(message, details) {
|
|
369
|
+
super(message, "GITHUB_ERROR", details);
|
|
370
|
+
this.name = "GitHubError";
|
|
371
|
+
}
|
|
372
|
+
};
|
|
369
373
|
}
|
|
370
374
|
});
|
|
371
375
|
|
|
@@ -694,8 +698,24 @@ var init_browser = __esm({
|
|
|
694
698
|
|
|
695
699
|
// src/services/auth.service.ts
|
|
696
700
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
701
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
702
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
703
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
697
704
|
import { ofetch } from "ofetch";
|
|
698
|
-
|
|
705
|
+
function getUserAgent() {
|
|
706
|
+
try {
|
|
707
|
+
const packageJsonPath2 = join3(__dirname3, "../../package.json");
|
|
708
|
+
const packageJson2 = JSON.parse(readFileSync2(packageJsonPath2, "utf-8"));
|
|
709
|
+
const version2 = packageJson2.version;
|
|
710
|
+
const platform = process.platform;
|
|
711
|
+
const arch = process.arch;
|
|
712
|
+
return `BragDuck-CLI/${version2} (${platform}-${arch})`;
|
|
713
|
+
} catch (error) {
|
|
714
|
+
logger.debug("Failed to read package.json version");
|
|
715
|
+
return "BragDuck-CLI/2.0.0";
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
var __filename3, __dirname3, AuthService, authService;
|
|
699
719
|
var init_auth_service = __esm({
|
|
700
720
|
"src/services/auth.service.ts"() {
|
|
701
721
|
"use strict";
|
|
@@ -706,10 +726,12 @@ var init_auth_service = __esm({
|
|
|
706
726
|
init_browser();
|
|
707
727
|
init_errors();
|
|
708
728
|
init_logger();
|
|
729
|
+
__filename3 = fileURLToPath3(import.meta.url);
|
|
730
|
+
__dirname3 = dirname2(__filename3);
|
|
709
731
|
AuthService = class {
|
|
710
732
|
apiBaseUrl;
|
|
711
733
|
constructor() {
|
|
712
|
-
this.apiBaseUrl = process.env.API_BASE_URL ||
|
|
734
|
+
this.apiBaseUrl = process.env.API_BASE_URL || "https://api.bragduck.com";
|
|
713
735
|
}
|
|
714
736
|
/**
|
|
715
737
|
* Generate a random state string for CSRF protection
|
|
@@ -745,7 +767,8 @@ var init_auth_service = __esm({
|
|
|
745
767
|
grant_type: "authorization_code"
|
|
746
768
|
},
|
|
747
769
|
headers: {
|
|
748
|
-
"Content-Type": "application/json"
|
|
770
|
+
"Content-Type": "application/json",
|
|
771
|
+
"User-Agent": getUserAgent()
|
|
749
772
|
}
|
|
750
773
|
}
|
|
751
774
|
);
|
|
@@ -861,7 +884,8 @@ var init_auth_service = __esm({
|
|
|
861
884
|
grant_type: "refresh_token"
|
|
862
885
|
},
|
|
863
886
|
headers: {
|
|
864
|
-
"Content-Type": "application/json"
|
|
887
|
+
"Content-Type": "application/json",
|
|
888
|
+
"User-Agent": getUserAgent()
|
|
865
889
|
}
|
|
866
890
|
}
|
|
867
891
|
);
|
|
@@ -893,15 +917,15 @@ __export(version_exports, {
|
|
|
893
917
|
getCurrentVersion: () => getCurrentVersion,
|
|
894
918
|
version: () => version
|
|
895
919
|
});
|
|
896
|
-
import { readFileSync as
|
|
897
|
-
import { fileURLToPath as
|
|
898
|
-
import { dirname as
|
|
920
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
921
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
922
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
899
923
|
import chalk4 from "chalk";
|
|
900
924
|
import boxen3 from "boxen";
|
|
901
925
|
function getCurrentVersion() {
|
|
902
926
|
try {
|
|
903
|
-
const packageJsonPath2 =
|
|
904
|
-
const packageJson2 = JSON.parse(
|
|
927
|
+
const packageJsonPath2 = join5(__dirname4, "../../package.json");
|
|
928
|
+
const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
|
|
905
929
|
return packageJson2.version;
|
|
906
930
|
} catch (error) {
|
|
907
931
|
logger.debug("Failed to read package.json version");
|
|
@@ -983,7 +1007,7 @@ function formatVersion(includePrefix = true) {
|
|
|
983
1007
|
const prefix = includePrefix ? "v" : "";
|
|
984
1008
|
return `${prefix}${version}`;
|
|
985
1009
|
}
|
|
986
|
-
var
|
|
1010
|
+
var __filename4, __dirname4, version;
|
|
987
1011
|
var init_version = __esm({
|
|
988
1012
|
"src/utils/version.ts"() {
|
|
989
1013
|
"use strict";
|
|
@@ -991,40 +1015,68 @@ var init_version = __esm({
|
|
|
991
1015
|
init_api_service();
|
|
992
1016
|
init_storage_service();
|
|
993
1017
|
init_logger();
|
|
994
|
-
|
|
995
|
-
|
|
1018
|
+
__filename4 = fileURLToPath4(import.meta.url);
|
|
1019
|
+
__dirname4 = dirname3(__filename4);
|
|
996
1020
|
version = getCurrentVersion();
|
|
997
1021
|
}
|
|
998
1022
|
});
|
|
999
1023
|
|
|
1000
1024
|
// src/services/api.service.ts
|
|
1001
1025
|
import { ofetch as ofetch2 } from "ofetch";
|
|
1002
|
-
|
|
1026
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1027
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1028
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1029
|
+
function getCliVersion() {
|
|
1030
|
+
try {
|
|
1031
|
+
const packageJsonPath2 = join6(__dirname5, "../../package.json");
|
|
1032
|
+
const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
|
|
1033
|
+
return packageJson2.version;
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
logger.debug("Failed to read package.json version");
|
|
1036
|
+
return "2.0.0";
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function getPlatformInfo() {
|
|
1040
|
+
const platform = process.platform;
|
|
1041
|
+
const arch = process.arch;
|
|
1042
|
+
return `${platform}-${arch}`;
|
|
1043
|
+
}
|
|
1044
|
+
var __filename5, __dirname5, ApiService, apiService;
|
|
1003
1045
|
var init_api_service = __esm({
|
|
1004
1046
|
"src/services/api.service.ts"() {
|
|
1005
1047
|
"use strict";
|
|
1006
1048
|
init_esm_shims();
|
|
1007
|
-
init_storage_service();
|
|
1008
1049
|
init_auth_service();
|
|
1009
1050
|
init_constants();
|
|
1010
1051
|
init_errors();
|
|
1011
1052
|
init_logger();
|
|
1053
|
+
__filename5 = fileURLToPath5(import.meta.url);
|
|
1054
|
+
__dirname5 = dirname4(__filename5);
|
|
1012
1055
|
ApiService = class {
|
|
1013
1056
|
baseURL;
|
|
1014
1057
|
client;
|
|
1015
1058
|
constructor() {
|
|
1016
|
-
this.baseURL = process.env.API_BASE_URL ||
|
|
1059
|
+
this.baseURL = process.env.API_BASE_URL || "https://api.bragduck.com";
|
|
1017
1060
|
this.client = ofetch2.create({
|
|
1018
1061
|
baseURL: this.baseURL,
|
|
1019
1062
|
// Request interceptor
|
|
1020
1063
|
onRequest: async ({ options }) => {
|
|
1021
1064
|
logger.debug(`API Request: ${options.method} ${options.baseURL}${options.url}`);
|
|
1065
|
+
const cliVersion = getCliVersion();
|
|
1066
|
+
const platform = getPlatformInfo();
|
|
1067
|
+
const userAgent = `BragDuck-CLI/${cliVersion} (${platform})`;
|
|
1022
1068
|
const token = await authService.getAccessToken();
|
|
1023
1069
|
if (token) {
|
|
1024
1070
|
options.headers = {
|
|
1025
1071
|
...options.headers,
|
|
1072
|
+
"User-Agent": userAgent,
|
|
1026
1073
|
Authorization: `Bearer ${token}`
|
|
1027
1074
|
};
|
|
1075
|
+
} else {
|
|
1076
|
+
options.headers = {
|
|
1077
|
+
...options.headers,
|
|
1078
|
+
"User-Agent": userAgent
|
|
1079
|
+
};
|
|
1028
1080
|
}
|
|
1029
1081
|
if (options.method && ["POST", "PUT", "PATCH"].includes(options.method)) {
|
|
1030
1082
|
options.headers = {
|
|
@@ -1204,7 +1256,6 @@ var init_api_service = __esm({
|
|
|
1204
1256
|
*/
|
|
1205
1257
|
setBaseURL(url) {
|
|
1206
1258
|
this.baseURL = url;
|
|
1207
|
-
storageService.setConfig("apiBaseUrl", url);
|
|
1208
1259
|
this.client = ofetch2.create({
|
|
1209
1260
|
baseURL: url
|
|
1210
1261
|
});
|
|
@@ -1223,9 +1274,9 @@ var init_api_service = __esm({
|
|
|
1223
1274
|
// src/cli.ts
|
|
1224
1275
|
init_esm_shims();
|
|
1225
1276
|
import { Command } from "commander";
|
|
1226
|
-
import { readFileSync as
|
|
1227
|
-
import { fileURLToPath as
|
|
1228
|
-
import { dirname as
|
|
1277
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1278
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
1279
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1229
1280
|
|
|
1230
1281
|
// src/commands/init.ts
|
|
1231
1282
|
init_esm_shims();
|
|
@@ -1399,6 +1450,13 @@ init_esm_shims();
|
|
|
1399
1450
|
import boxen4 from "boxen";
|
|
1400
1451
|
import chalk7 from "chalk";
|
|
1401
1452
|
|
|
1453
|
+
// src/services/github.service.ts
|
|
1454
|
+
init_esm_shims();
|
|
1455
|
+
init_errors();
|
|
1456
|
+
init_logger();
|
|
1457
|
+
import { exec as exec2 } from "child_process";
|
|
1458
|
+
import { promisify as promisify2 } from "util";
|
|
1459
|
+
|
|
1402
1460
|
// src/services/git.service.ts
|
|
1403
1461
|
init_esm_shims();
|
|
1404
1462
|
import simpleGit from "simple-git";
|
|
@@ -1407,9 +1465,9 @@ import simpleGit from "simple-git";
|
|
|
1407
1465
|
init_esm_shims();
|
|
1408
1466
|
init_errors();
|
|
1409
1467
|
import { existsSync as existsSync2 } from "fs";
|
|
1410
|
-
import { join as
|
|
1468
|
+
import { join as join4 } from "path";
|
|
1411
1469
|
function validateGitRepository(path2) {
|
|
1412
|
-
const gitDir =
|
|
1470
|
+
const gitDir = join4(path2, ".git");
|
|
1413
1471
|
if (!existsSync2(gitDir)) {
|
|
1414
1472
|
throw new GitError(
|
|
1415
1473
|
"Not a git repository. Please run this command from within a git repository.",
|
|
@@ -1481,7 +1539,7 @@ var GitService = class {
|
|
|
1481
1539
|
* Fetch recent commits
|
|
1482
1540
|
*/
|
|
1483
1541
|
async getRecentCommits(options = {}) {
|
|
1484
|
-
const { days = 30, limit, author
|
|
1542
|
+
const { days = 30, limit, author } = options;
|
|
1485
1543
|
try {
|
|
1486
1544
|
await this.validateRepository();
|
|
1487
1545
|
logger.debug(`Fetching commits from last ${days} days`);
|
|
@@ -1619,6 +1677,203 @@ var GitService = class {
|
|
|
1619
1677
|
};
|
|
1620
1678
|
var gitService = new GitService();
|
|
1621
1679
|
|
|
1680
|
+
// src/services/github.service.ts
|
|
1681
|
+
var execAsync2 = promisify2(exec2);
|
|
1682
|
+
var GitHubService = class {
|
|
1683
|
+
MAX_BODY_LENGTH = 5e3;
|
|
1684
|
+
PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
|
|
1685
|
+
/**
|
|
1686
|
+
* Check if GitHub CLI is installed and available
|
|
1687
|
+
*/
|
|
1688
|
+
async checkGitHubCLI() {
|
|
1689
|
+
try {
|
|
1690
|
+
await execAsync2("command gh --version");
|
|
1691
|
+
return true;
|
|
1692
|
+
} catch (_error) {
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Validate that GitHub CLI is installed
|
|
1698
|
+
*/
|
|
1699
|
+
async ensureGitHubCLI() {
|
|
1700
|
+
const isInstalled = await this.checkGitHubCLI();
|
|
1701
|
+
if (!isInstalled) {
|
|
1702
|
+
throw new GitHubError("GitHub CLI (gh) is not installed", {
|
|
1703
|
+
hint: "Install from https://cli.github.com/"
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Check if user is authenticated with GitHub CLI
|
|
1709
|
+
*/
|
|
1710
|
+
async checkAuthentication() {
|
|
1711
|
+
try {
|
|
1712
|
+
await execAsync2("command gh auth status");
|
|
1713
|
+
return true;
|
|
1714
|
+
} catch (_error) {
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Ensure user is authenticated with GitHub CLI
|
|
1720
|
+
*/
|
|
1721
|
+
async ensureAuthentication() {
|
|
1722
|
+
const isAuthenticated = await this.checkAuthentication();
|
|
1723
|
+
if (!isAuthenticated) {
|
|
1724
|
+
throw new GitHubError("Not authenticated with GitHub", {
|
|
1725
|
+
hint: 'Run "gh auth login" to authenticate'
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Validate that the current repository is hosted on GitHub
|
|
1731
|
+
*/
|
|
1732
|
+
async validateGitHubRepository() {
|
|
1733
|
+
try {
|
|
1734
|
+
await this.ensureGitHubCLI();
|
|
1735
|
+
await this.ensureAuthentication();
|
|
1736
|
+
await gitService.validateRepository();
|
|
1737
|
+
const { stdout } = await execAsync2("command gh repo view --json url");
|
|
1738
|
+
const data = JSON.parse(stdout);
|
|
1739
|
+
if (!data.url) {
|
|
1740
|
+
throw new GitHubError("This repository is not hosted on GitHub", {
|
|
1741
|
+
hint: "Only GitHub repositories are currently supported for PR scanning"
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
} catch (error) {
|
|
1745
|
+
if (error instanceof GitHubError || error instanceof GitError) {
|
|
1746
|
+
throw error;
|
|
1747
|
+
}
|
|
1748
|
+
if (error.message?.includes("not a git repository")) {
|
|
1749
|
+
throw new GitHubError("Not a git repository", {
|
|
1750
|
+
hint: "Navigate to a git repository directory"
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
if (error.message?.includes("Could not resolve to a Repository")) {
|
|
1754
|
+
throw new GitHubError("This repository is not hosted on GitHub", {
|
|
1755
|
+
hint: "Only GitHub repositories are currently supported for PR scanning"
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
throw new GitHubError("Failed to validate GitHub repository", {
|
|
1759
|
+
originalError: error.message
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Get GitHub repository information
|
|
1765
|
+
*/
|
|
1766
|
+
async getRepositoryInfo() {
|
|
1767
|
+
try {
|
|
1768
|
+
await this.ensureGitHubCLI();
|
|
1769
|
+
const { stdout } = await execAsync2(
|
|
1770
|
+
"command gh repo view --json owner,name,url,nameWithOwner"
|
|
1771
|
+
);
|
|
1772
|
+
const data = JSON.parse(stdout);
|
|
1773
|
+
return {
|
|
1774
|
+
owner: data.owner.login,
|
|
1775
|
+
name: data.name,
|
|
1776
|
+
fullName: data.nameWithOwner,
|
|
1777
|
+
url: data.url,
|
|
1778
|
+
isGitHub: true
|
|
1779
|
+
};
|
|
1780
|
+
} catch (error) {
|
|
1781
|
+
if (error instanceof GitHubError || error instanceof GitError) {
|
|
1782
|
+
throw error;
|
|
1783
|
+
}
|
|
1784
|
+
throw new GitHubError("Failed to get GitHub repository information", {
|
|
1785
|
+
originalError: error.message
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Get the current authenticated GitHub user
|
|
1791
|
+
*/
|
|
1792
|
+
async getCurrentGitHubUser() {
|
|
1793
|
+
try {
|
|
1794
|
+
const { stdout } = await execAsync2("command gh api user --jq .login");
|
|
1795
|
+
return stdout.trim() || null;
|
|
1796
|
+
} catch (_error) {
|
|
1797
|
+
logger.debug("Failed to get GitHub user");
|
|
1798
|
+
return null;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Fetch merged PRs from GitHub
|
|
1803
|
+
*/
|
|
1804
|
+
async getMergedPRs(options = {}) {
|
|
1805
|
+
const { days = 30, limit, author } = options;
|
|
1806
|
+
try {
|
|
1807
|
+
await this.ensureGitHubCLI();
|
|
1808
|
+
await this.ensureAuthentication();
|
|
1809
|
+
logger.debug(`Fetching merged PRs from the last ${days} days`);
|
|
1810
|
+
const since = /* @__PURE__ */ new Date();
|
|
1811
|
+
since.setDate(since.getDate() - days);
|
|
1812
|
+
const sinceDate = since.toISOString().split("T")[0];
|
|
1813
|
+
let searchQuery = `merged:>=${sinceDate}`;
|
|
1814
|
+
if (author) {
|
|
1815
|
+
searchQuery += ` author:${author}`;
|
|
1816
|
+
}
|
|
1817
|
+
const limitArg = limit ? `--limit ${limit}` : "";
|
|
1818
|
+
const command = `command gh pr list --state merged --json ${this.PR_SEARCH_FIELDS} --search "${searchQuery}" ${limitArg}`;
|
|
1819
|
+
logger.debug(`Running: ${command}`);
|
|
1820
|
+
const { stdout } = await execAsync2(command);
|
|
1821
|
+
const prs = JSON.parse(stdout);
|
|
1822
|
+
logger.debug(`Found ${prs.length} merged PRs`);
|
|
1823
|
+
return prs;
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
if (error instanceof GitHubError || error instanceof GitError) {
|
|
1826
|
+
throw error;
|
|
1827
|
+
}
|
|
1828
|
+
throw new GitHubError("Failed to fetch PRs from GitHub", {
|
|
1829
|
+
originalError: error.message,
|
|
1830
|
+
days
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Get merged PRs by current user (PRs authored by the current user)
|
|
1836
|
+
*/
|
|
1837
|
+
async getPRsByCurrentUser(options = {}) {
|
|
1838
|
+
const currentUser = await this.getCurrentGitHubUser();
|
|
1839
|
+
if (!currentUser) {
|
|
1840
|
+
logger.warning("Could not determine GitHub user, returning all PRs");
|
|
1841
|
+
return this.getMergedPRs(options);
|
|
1842
|
+
}
|
|
1843
|
+
logger.debug(`Filtering PRs by author: ${currentUser}`);
|
|
1844
|
+
return this.getMergedPRs({
|
|
1845
|
+
...options,
|
|
1846
|
+
author: currentUser
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Transform a GitHub PR into a GitCommit structure
|
|
1851
|
+
* This allows PR data to work with existing commit-based UI and API
|
|
1852
|
+
*/
|
|
1853
|
+
transformPRToCommit(pr) {
|
|
1854
|
+
const title = pr.title;
|
|
1855
|
+
const body = pr.body || "";
|
|
1856
|
+
const truncatedBody = body.length > this.MAX_BODY_LENGTH ? body.substring(0, this.MAX_BODY_LENGTH) + "...[truncated]" : body;
|
|
1857
|
+
const message = truncatedBody ? `${title}
|
|
1858
|
+
|
|
1859
|
+
${truncatedBody}` : title;
|
|
1860
|
+
return {
|
|
1861
|
+
sha: `pr-${pr.number}`,
|
|
1862
|
+
message,
|
|
1863
|
+
author: pr.author.login,
|
|
1864
|
+
authorEmail: "",
|
|
1865
|
+
// Not available from gh PR API
|
|
1866
|
+
date: pr.mergedAt,
|
|
1867
|
+
diffStats: {
|
|
1868
|
+
filesChanged: pr.changedFiles,
|
|
1869
|
+
insertions: pr.additions,
|
|
1870
|
+
deletions: pr.deletions
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
var githubService = new GitHubService();
|
|
1876
|
+
|
|
1622
1877
|
// src/commands/scan.ts
|
|
1623
1878
|
init_api_service();
|
|
1624
1879
|
init_auth_service();
|
|
@@ -1635,7 +1890,13 @@ import chalk5 from "chalk";
|
|
|
1635
1890
|
import Table from "cli-table3";
|
|
1636
1891
|
import terminalLink from "terminal-link";
|
|
1637
1892
|
function formatCommitChoice(commit) {
|
|
1638
|
-
|
|
1893
|
+
let displaySha;
|
|
1894
|
+
if (commit.sha.startsWith("pr-")) {
|
|
1895
|
+
const prNumber = commit.sha.replace("pr-", "#");
|
|
1896
|
+
displaySha = chalk5.yellow(prNumber);
|
|
1897
|
+
} else {
|
|
1898
|
+
displaySha = chalk5.yellow(commit.sha.substring(0, 7));
|
|
1899
|
+
}
|
|
1639
1900
|
const message = commit.message.split("\n")[0];
|
|
1640
1901
|
const author = chalk5.gray(`by ${commit.author}`);
|
|
1641
1902
|
const date = chalk5.gray(new Date(commit.date).toLocaleDateString());
|
|
@@ -1646,7 +1907,7 @@ function formatCommitChoice(commit) {
|
|
|
1646
1907
|
` [${filesChanged} files, ${chalk5.green(`+${insertions}`)} ${chalk5.red(`-${deletions}`)}]`
|
|
1647
1908
|
);
|
|
1648
1909
|
}
|
|
1649
|
-
return `${
|
|
1910
|
+
return `${displaySha} ${message}${stats}
|
|
1650
1911
|
${author} \u2022 ${date}`;
|
|
1651
1912
|
}
|
|
1652
1913
|
function formatRefinedCommitsTable(commits) {
|
|
@@ -1721,7 +1982,7 @@ async function promptSelectCommits(commits) {
|
|
|
1721
1982
|
checked: false
|
|
1722
1983
|
}));
|
|
1723
1984
|
const selected = await checkbox({
|
|
1724
|
-
message: "Select
|
|
1985
|
+
message: "Select PRs to brag about (use space to select, enter to confirm):",
|
|
1725
1986
|
choices,
|
|
1726
1987
|
pageSize: 10,
|
|
1727
1988
|
loop: false
|
|
@@ -1743,7 +2004,7 @@ async function promptDaysToScan(defaultDays = 30) {
|
|
|
1743
2004
|
{ name: "Custom", value: "custom", description: "Enter custom number of days" }
|
|
1744
2005
|
];
|
|
1745
2006
|
const selected = await select({
|
|
1746
|
-
message: "How many days back should we scan for
|
|
2007
|
+
message: "How many days back should we scan for PRs?",
|
|
1747
2008
|
choices,
|
|
1748
2009
|
default: "30"
|
|
1749
2010
|
});
|
|
@@ -1775,8 +2036,8 @@ function createSpinner(text) {
|
|
|
1775
2036
|
spinner: "dots"
|
|
1776
2037
|
});
|
|
1777
2038
|
}
|
|
1778
|
-
function
|
|
1779
|
-
return createSpinner(`Fetching
|
|
2039
|
+
function fetchingPRsSpinner(days) {
|
|
2040
|
+
return createSpinner(`Fetching merged PRs from the last ${days} days...`);
|
|
1780
2041
|
}
|
|
1781
2042
|
function refiningCommitsSpinner(count) {
|
|
1782
2043
|
return createSpinner(`Refining ${count} commit${count > 1 ? "s" : ""} with AI...`);
|
|
@@ -1823,9 +2084,9 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
|
|
|
1823
2084
|
);
|
|
1824
2085
|
process.exit(1);
|
|
1825
2086
|
}
|
|
1826
|
-
await
|
|
1827
|
-
const repoInfo = await
|
|
1828
|
-
logger.info(`Repository: ${chalk7.cyan(repoInfo.
|
|
2087
|
+
await githubService.validateGitHubRepository();
|
|
2088
|
+
const repoInfo = await githubService.getRepositoryInfo();
|
|
2089
|
+
logger.info(`Repository: ${chalk7.cyan(repoInfo.fullName)} on GitHub`);
|
|
1829
2090
|
logger.log("");
|
|
1830
2091
|
let days = options.days;
|
|
1831
2092
|
if (!days) {
|
|
@@ -1833,21 +2094,22 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
|
|
|
1833
2094
|
days = await promptDaysToScan(defaultDays);
|
|
1834
2095
|
logger.log("");
|
|
1835
2096
|
}
|
|
1836
|
-
const spinner =
|
|
2097
|
+
const spinner = fetchingPRsSpinner(days);
|
|
1837
2098
|
spinner.start();
|
|
1838
|
-
let
|
|
2099
|
+
let prs;
|
|
1839
2100
|
if (options.all) {
|
|
1840
|
-
|
|
2101
|
+
prs = await githubService.getMergedPRs({ days });
|
|
1841
2102
|
} else {
|
|
1842
|
-
|
|
2103
|
+
prs = await githubService.getPRsByCurrentUser({ days });
|
|
1843
2104
|
}
|
|
2105
|
+
const commits = prs.map((pr) => githubService.transformPRToCommit(pr));
|
|
1844
2106
|
if (commits.length === 0) {
|
|
1845
|
-
failSpinner(spinner, `No
|
|
2107
|
+
failSpinner(spinner, `No merged PRs found in the last ${days} days`);
|
|
1846
2108
|
logger.log("");
|
|
1847
|
-
logger.info("Try increasing the number of days or check your
|
|
2109
|
+
logger.info("Try increasing the number of days or check your GitHub activity");
|
|
1848
2110
|
return;
|
|
1849
2111
|
}
|
|
1850
|
-
succeedSpinner(spinner, `Found ${commits.length}
|
|
2112
|
+
succeedSpinner(spinner, `Found ${commits.length} PR${commits.length > 1 ? "s" : ""}`);
|
|
1851
2113
|
logger.log("");
|
|
1852
2114
|
logger.log(formatCommitStats(commits));
|
|
1853
2115
|
logger.log("");
|
|
@@ -1858,9 +2120,7 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
|
|
|
1858
2120
|
}
|
|
1859
2121
|
const selectedCommits = commits.filter((c) => selectedShas.includes(c.sha));
|
|
1860
2122
|
logger.log("");
|
|
1861
|
-
logger.success(
|
|
1862
|
-
`Selected ${selectedCommits.length} commit${selectedCommits.length > 1 ? "s" : ""}`
|
|
1863
|
-
);
|
|
2123
|
+
logger.success(`Selected ${selectedCommits.length} PR${selectedCommits.length > 1 ? "s" : ""}`);
|
|
1864
2124
|
logger.log("");
|
|
1865
2125
|
const refineSpinner = refiningCommitsSpinner(selectedCommits.length);
|
|
1866
2126
|
refineSpinner.start();
|
|
@@ -1879,7 +2139,7 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
|
|
|
1879
2139
|
};
|
|
1880
2140
|
const refineResponse = await apiService.refineCommits(refineRequest);
|
|
1881
2141
|
const refinedCommits = refineResponse.refined_commits;
|
|
1882
|
-
succeedSpinner(refineSpinner, "
|
|
2142
|
+
succeedSpinner(refineSpinner, "PRs refined successfully");
|
|
1883
2143
|
logger.log("");
|
|
1884
2144
|
logger.info("Preview of refined brags:");
|
|
1885
2145
|
logger.log("");
|
|
@@ -1901,7 +2161,7 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
|
|
|
1901
2161
|
title: refined.refined_title,
|
|
1902
2162
|
description: refined.refined_description,
|
|
1903
2163
|
tags: refined.suggested_tags,
|
|
1904
|
-
repository: repoInfo.
|
|
2164
|
+
repository: repoInfo.url,
|
|
1905
2165
|
date: originalCommit?.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1906
2166
|
commit_url: refined.commit_url,
|
|
1907
2167
|
impact_score: refined.impact_score,
|
|
@@ -1935,8 +2195,11 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
|
|
|
1935
2195
|
}
|
|
1936
2196
|
}
|
|
1937
2197
|
function getErrorHint2(error) {
|
|
2198
|
+
if (error.name === "GitHubError") {
|
|
2199
|
+
return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
|
|
2200
|
+
}
|
|
1938
2201
|
if (error.name === "GitError") {
|
|
1939
|
-
return "Make sure you are in a git repository
|
|
2202
|
+
return "Make sure you are in a git repository. Note: Only GitHub repositories are supported for PR scanning.";
|
|
1940
2203
|
}
|
|
1941
2204
|
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
1942
2205
|
return 'Run "bragduck init" to login again';
|
|
@@ -2102,7 +2365,6 @@ function getErrorHint3(error) {
|
|
|
2102
2365
|
// src/commands/config.ts
|
|
2103
2366
|
init_esm_shims();
|
|
2104
2367
|
init_storage_service();
|
|
2105
|
-
init_api_service();
|
|
2106
2368
|
init_logger();
|
|
2107
2369
|
init_constants();
|
|
2108
2370
|
import boxen6 from "boxen";
|
|
@@ -2152,7 +2414,6 @@ async function configCommand(subcommand, key, value) {
|
|
|
2152
2414
|
}
|
|
2153
2415
|
async function handleListConfig() {
|
|
2154
2416
|
const config2 = {
|
|
2155
|
-
apiBaseUrl: storageService.getConfig("apiBaseUrl"),
|
|
2156
2417
|
defaultCommitDays: storageService.getConfig("defaultCommitDays"),
|
|
2157
2418
|
autoVersionCheck: storageService.getConfig("autoVersionCheck")
|
|
2158
2419
|
};
|
|
@@ -2165,11 +2426,6 @@ async function handleListConfig() {
|
|
|
2165
2426
|
border: ["gray"]
|
|
2166
2427
|
}
|
|
2167
2428
|
});
|
|
2168
|
-
table.push([
|
|
2169
|
-
chalk9.white("apiBaseUrl"),
|
|
2170
|
-
chalk9.yellow(config2.apiBaseUrl),
|
|
2171
|
-
chalk9.dim(DEFAULT_CONFIG.apiBaseUrl)
|
|
2172
|
-
]);
|
|
2173
2429
|
table.push([
|
|
2174
2430
|
chalk9.white("defaultCommitDays"),
|
|
2175
2431
|
chalk9.yellow(String(config2.defaultCommitDays)),
|
|
@@ -2209,9 +2465,6 @@ async function handleSetConfig(key, value) {
|
|
|
2209
2465
|
validateConfigKey(key);
|
|
2210
2466
|
const typedValue = validateAndConvertValue(key, value);
|
|
2211
2467
|
storageService.setConfig(key, typedValue);
|
|
2212
|
-
if (key === CONFIG_KEYS.API_BASE_URL) {
|
|
2213
|
-
apiService.setBaseURL(typedValue);
|
|
2214
|
-
}
|
|
2215
2468
|
logger.log(
|
|
2216
2469
|
boxen6(
|
|
2217
2470
|
`${chalk9.green.bold("\u2713 Configuration updated")}
|
|
@@ -2238,15 +2491,6 @@ ${VALID_CONFIG_KEYS.map((k) => ` - ${k}`).join("\n")}`
|
|
|
2238
2491
|
}
|
|
2239
2492
|
function validateAndConvertValue(key, value) {
|
|
2240
2493
|
switch (key) {
|
|
2241
|
-
case CONFIG_KEYS.API_BASE_URL:
|
|
2242
|
-
if (!isValidUrl(value)) {
|
|
2243
|
-
throw new ValidationError(
|
|
2244
|
-
`Invalid URL format: "${value}"
|
|
2245
|
-
|
|
2246
|
-
Example: https://api.bragduck.com`
|
|
2247
|
-
);
|
|
2248
|
-
}
|
|
2249
|
-
return value;
|
|
2250
2494
|
case CONFIG_KEYS.DEFAULT_COMMIT_DAYS:
|
|
2251
2495
|
const days = parseInt(value, 10);
|
|
2252
2496
|
if (isNaN(days) || days < 1 || days > 365) {
|
|
@@ -2270,15 +2514,7 @@ Must be a number between 1 and 365`
|
|
|
2270
2514
|
Must be one of: true, false, yes, no, 1, 0`
|
|
2271
2515
|
);
|
|
2272
2516
|
default:
|
|
2273
|
-
|
|
2274
|
-
}
|
|
2275
|
-
}
|
|
2276
|
-
function isValidUrl(url) {
|
|
2277
|
-
try {
|
|
2278
|
-
const parsed = new URL(url);
|
|
2279
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
2280
|
-
} catch {
|
|
2281
|
-
return false;
|
|
2517
|
+
throw new ValidationError(`Unknown config key: "${key}"`);
|
|
2282
2518
|
}
|
|
2283
2519
|
}
|
|
2284
2520
|
function getConfigHint(error) {
|
|
@@ -2291,10 +2527,10 @@ function getConfigHint(error) {
|
|
|
2291
2527
|
// src/cli.ts
|
|
2292
2528
|
init_version();
|
|
2293
2529
|
init_logger();
|
|
2294
|
-
var
|
|
2295
|
-
var
|
|
2296
|
-
var packageJsonPath =
|
|
2297
|
-
var packageJson = JSON.parse(
|
|
2530
|
+
var __filename6 = fileURLToPath6(import.meta.url);
|
|
2531
|
+
var __dirname6 = dirname5(__filename6);
|
|
2532
|
+
var packageJsonPath = join7(__dirname6, "../../package.json");
|
|
2533
|
+
var packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
2298
2534
|
var program = new Command();
|
|
2299
2535
|
program.name("bragduck").description("CLI tool for managing developer achievements and brags").version(packageJson.version, "-v, --version", "Display version number").helpOption("-h, --help", "Display help information").option("--skip-version-check", "Skip automatic version check on startup").option("--debug", "Enable debug mode (shows detailed logs)");
|
|
2300
2536
|
program.command("init").description("Authenticate with Bragduck").action(async () => {
|