@bragduck/cli 2.28.1 → 2.30.2
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 +138 -300
- package/dist/bin/bragduck.js +611 -705
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +89 -83
- package/dist/index.js.map +1 -1
- package/package.json +3 -12
package/dist/bin/bragduck.js
CHANGED
|
@@ -9,11 +9,11 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// node_modules/tsup/assets/esm_shims.js
|
|
12
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js
|
|
13
13
|
import path from "path";
|
|
14
14
|
import { fileURLToPath } from "url";
|
|
15
15
|
var init_esm_shims = __esm({
|
|
16
|
-
"node_modules/tsup/assets/esm_shims.js"() {
|
|
16
|
+
"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js"() {
|
|
17
17
|
"use strict";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
@@ -22,7 +22,7 @@ var init_esm_shims = __esm({
|
|
|
22
22
|
import { config } from "dotenv";
|
|
23
23
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24
24
|
import { dirname, join } from "path";
|
|
25
|
-
var __filename, __dirname, APP_NAME, CONFIG_KEYS, DEFAULT_CONFIG, OAUTH_CONFIG, API_ENDPOINTS, ENCRYPTION_CONFIG, STORAGE_PATHS, HTTP_STATUS, CONFIG_FILES;
|
|
25
|
+
var __filename, __dirname, APP_NAME, CONFIG_KEYS, DEFAULT_CONFIG, OAUTH_CONFIG, ATLASSIAN_OAUTH_CONFIG, API_ENDPOINTS, ENCRYPTION_CONFIG, STORAGE_PATHS, HTTP_STATUS, CONFIG_FILES;
|
|
26
26
|
var init_constants = __esm({
|
|
27
27
|
"src/constants.ts"() {
|
|
28
28
|
"use strict";
|
|
@@ -55,13 +55,25 @@ var init_constants = __esm({
|
|
|
55
55
|
TIMEOUT_MS: 12e4,
|
|
56
56
|
// 2 minutes
|
|
57
57
|
MIN_PORT: 8e3,
|
|
58
|
-
MAX_PORT:
|
|
58
|
+
MAX_PORT: 8004
|
|
59
|
+
};
|
|
60
|
+
ATLASSIAN_OAUTH_CONFIG = {
|
|
61
|
+
CLIENT_ID: "kJlsd66DLTnENE8p7Ru2JwqZg1Sie4yZ",
|
|
62
|
+
AUTH_URL: "https://auth.atlassian.com/authorize",
|
|
63
|
+
AUDIENCE: "api.atlassian.com",
|
|
64
|
+
SCOPES: "read:jira-work read:jira-user read:page:confluence read:confluence-user offline_access read:me",
|
|
65
|
+
ACCESSIBLE_RESOURCES_URL: "https://api.atlassian.com/oauth/token/accessible-resources",
|
|
66
|
+
API_GATEWAY_URL: "https://api.atlassian.com"
|
|
59
67
|
};
|
|
60
68
|
API_ENDPOINTS = {
|
|
61
69
|
AUTH: {
|
|
62
70
|
INITIATE: "/v1/auth/cli/initiate",
|
|
63
71
|
TOKEN: "/v1/auth/cli/token"
|
|
64
72
|
},
|
|
73
|
+
ATLASSIAN: {
|
|
74
|
+
TOKEN: "/v1/auth/atlassian/token",
|
|
75
|
+
REFRESH: "/v1/auth/atlassian/refresh"
|
|
76
|
+
},
|
|
65
77
|
BRAGS: {
|
|
66
78
|
CREATE: "/v1/brags",
|
|
67
79
|
LIST: "/v1/brags",
|
|
@@ -157,7 +169,7 @@ var init_env_loader = __esm({
|
|
|
157
169
|
});
|
|
158
170
|
|
|
159
171
|
// src/utils/errors.ts
|
|
160
|
-
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError;
|
|
172
|
+
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError, AtlassianError;
|
|
161
173
|
var init_errors = __esm({
|
|
162
174
|
"src/utils/errors.ts"() {
|
|
163
175
|
"use strict";
|
|
@@ -248,6 +260,12 @@ var init_errors = __esm({
|
|
|
248
260
|
this.name = "ConfluenceError";
|
|
249
261
|
}
|
|
250
262
|
};
|
|
263
|
+
AtlassianError = class extends BragduckError {
|
|
264
|
+
constructor(message, details) {
|
|
265
|
+
super(message, "ATLASSIAN_ERROR", details);
|
|
266
|
+
this.name = "AtlassianError";
|
|
267
|
+
}
|
|
268
|
+
};
|
|
251
269
|
}
|
|
252
270
|
});
|
|
253
271
|
|
|
@@ -770,92 +788,95 @@ async function findAvailablePort() {
|
|
|
770
788
|
async function startOAuthCallbackServer(expectedState) {
|
|
771
789
|
const port = await findAvailablePort();
|
|
772
790
|
const timeout = OAUTH_CONFIG.TIMEOUT_MS;
|
|
773
|
-
|
|
791
|
+
const callbackUrl = `http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`;
|
|
792
|
+
return new Promise((resolveHandle, rejectHandle) => {
|
|
774
793
|
let server = null;
|
|
775
794
|
let timeoutId = null;
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (server) {
|
|
781
|
-
if (typeof server.closeAllConnections === "function") {
|
|
782
|
-
server.closeAllConnections();
|
|
783
|
-
}
|
|
784
|
-
server.close(() => {
|
|
785
|
-
logger.debug("OAuth server closed");
|
|
786
|
-
});
|
|
787
|
-
server.unref();
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
const handleRequest = (req, res) => {
|
|
791
|
-
const parsedUrl = parse(req.url || "", true);
|
|
792
|
-
logger.debug(`OAuth callback received: ${req.url}`);
|
|
793
|
-
if (parsedUrl.pathname === OAUTH_CONFIG.CALLBACK_PATH) {
|
|
794
|
-
const { code, state, error, error_description } = parsedUrl.query;
|
|
795
|
-
if (error) {
|
|
796
|
-
const errorMsg = error_description || error;
|
|
797
|
-
logger.debug(`OAuth error: ${errorMsg}`);
|
|
798
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
799
|
-
res.end(ERROR_HTML(String(errorMsg)));
|
|
800
|
-
cleanup();
|
|
801
|
-
reject(new OAuthError(`OAuth error: ${errorMsg}`));
|
|
802
|
-
return;
|
|
795
|
+
const resultPromise = new Promise((resolveResult, rejectResult) => {
|
|
796
|
+
const cleanup = () => {
|
|
797
|
+
if (timeoutId) {
|
|
798
|
+
globalThis.clearTimeout(timeoutId);
|
|
803
799
|
}
|
|
804
|
-
if (
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
800
|
+
if (server) {
|
|
801
|
+
if (typeof server.closeAllConnections === "function") {
|
|
802
|
+
server.closeAllConnections();
|
|
803
|
+
}
|
|
804
|
+
server.close(() => {
|
|
805
|
+
logger.debug("OAuth server closed");
|
|
806
|
+
});
|
|
807
|
+
server.unref();
|
|
812
808
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
809
|
+
};
|
|
810
|
+
const handleRequest = (req, res) => {
|
|
811
|
+
const parsedUrl = parse(req.url || "", true);
|
|
812
|
+
logger.debug(`OAuth callback received: ${req.url}`);
|
|
813
|
+
if (parsedUrl.pathname === OAUTH_CONFIG.CALLBACK_PATH) {
|
|
814
|
+
const { code, state, error, error_description } = parsedUrl.query;
|
|
815
|
+
if (error) {
|
|
816
|
+
const errorMsg = error_description || error;
|
|
817
|
+
logger.debug(`OAuth error: ${errorMsg}`);
|
|
818
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
819
|
+
res.end(ERROR_HTML(String(errorMsg)));
|
|
820
|
+
cleanup();
|
|
821
|
+
rejectResult(new OAuthError(`OAuth error: ${errorMsg}`));
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
if (!code || !state) {
|
|
825
|
+
const errorMsg = "Missing code or state parameter";
|
|
826
|
+
logger.debug(errorMsg);
|
|
827
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
828
|
+
res.end(ERROR_HTML(errorMsg));
|
|
829
|
+
cleanup();
|
|
830
|
+
rejectResult(new OAuthError(errorMsg));
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (state !== expectedState) {
|
|
834
|
+
const errorMsg = "Invalid state parameter (possible CSRF attack)";
|
|
835
|
+
logger.debug(errorMsg);
|
|
836
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
837
|
+
res.end(ERROR_HTML(errorMsg));
|
|
838
|
+
cleanup();
|
|
839
|
+
rejectResult(new OAuthError(errorMsg));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
843
|
+
res.end(SUCCESS_HTML);
|
|
844
|
+
globalThis.setTimeout(() => {
|
|
845
|
+
cleanup();
|
|
846
|
+
resolveResult({
|
|
847
|
+
code: String(code),
|
|
848
|
+
state: String(state),
|
|
849
|
+
port
|
|
850
|
+
});
|
|
851
|
+
}, 100);
|
|
820
852
|
return;
|
|
821
853
|
}
|
|
822
|
-
res.writeHead(
|
|
823
|
-
res.end(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
`OAuth callback server listening on http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`
|
|
846
|
-
);
|
|
854
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
855
|
+
res.end("Not Found");
|
|
856
|
+
};
|
|
857
|
+
server = createServer(handleRequest);
|
|
858
|
+
server.on("error", (error) => {
|
|
859
|
+
logger.debug(`OAuth server error: ${error.message}`);
|
|
860
|
+
cleanup();
|
|
861
|
+
rejectResult(new OAuthError(`OAuth server error: ${error.message}`));
|
|
862
|
+
rejectHandle(new OAuthError(`OAuth server error: ${error.message}`));
|
|
863
|
+
});
|
|
864
|
+
server.listen(port, "127.0.0.1", () => {
|
|
865
|
+
logger.debug(
|
|
866
|
+
`OAuth callback server listening on http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`
|
|
867
|
+
);
|
|
868
|
+
resolveHandle({ callbackUrl, resultPromise });
|
|
869
|
+
});
|
|
870
|
+
timeoutId = globalThis.setTimeout(() => {
|
|
871
|
+
logger.debug("OAuth callback timeout");
|
|
872
|
+
cleanup();
|
|
873
|
+
rejectResult(
|
|
874
|
+
new OAuthError("Authentication timeout - no callback received within 2 minutes")
|
|
875
|
+
);
|
|
876
|
+
}, timeout);
|
|
847
877
|
});
|
|
848
|
-
timeoutId = globalThis.setTimeout(() => {
|
|
849
|
-
logger.debug("OAuth callback timeout");
|
|
850
|
-
cleanup();
|
|
851
|
-
reject(new OAuthError("Authentication timeout - no callback received within 2 minutes"));
|
|
852
|
-
}, timeout);
|
|
853
878
|
});
|
|
854
879
|
}
|
|
855
|
-
async function getCallbackUrl() {
|
|
856
|
-
const port = await findAvailablePort();
|
|
857
|
-
return `http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`;
|
|
858
|
-
}
|
|
859
880
|
var SUCCESS_HTML, ERROR_HTML;
|
|
860
881
|
var init_oauth_server = __esm({
|
|
861
882
|
"src/utils/oauth-server.ts"() {
|
|
@@ -1118,7 +1139,7 @@ var init_auth_service = __esm({
|
|
|
1118
1139
|
async login() {
|
|
1119
1140
|
logger.debug("Starting OAuth login flow");
|
|
1120
1141
|
const state = this.generateState();
|
|
1121
|
-
const callbackUrl = await
|
|
1142
|
+
const { callbackUrl, resultPromise } = await startOAuthCallbackServer(state);
|
|
1122
1143
|
storageService.setOAuthState({
|
|
1123
1144
|
state,
|
|
1124
1145
|
createdAt: Date.now()
|
|
@@ -1127,7 +1148,6 @@ var init_auth_service = __esm({
|
|
|
1127
1148
|
logger.debug(`Callback URL: ${callbackUrl}`);
|
|
1128
1149
|
const authUrl = await this.buildAuthUrl(state, callbackUrl);
|
|
1129
1150
|
logger.debug(`Authorization URL: ${authUrl}`);
|
|
1130
|
-
const serverPromise = startOAuthCallbackServer(state);
|
|
1131
1151
|
try {
|
|
1132
1152
|
await openBrowser(authUrl);
|
|
1133
1153
|
} catch {
|
|
@@ -1137,7 +1157,7 @@ var init_auth_service = __esm({
|
|
|
1137
1157
|
}
|
|
1138
1158
|
let callbackResult;
|
|
1139
1159
|
try {
|
|
1140
|
-
callbackResult = await
|
|
1160
|
+
callbackResult = await resultPromise;
|
|
1141
1161
|
} catch (error) {
|
|
1142
1162
|
storageService.deleteOAuthState();
|
|
1143
1163
|
throw error;
|
|
@@ -1241,15 +1261,15 @@ __export(version_exports, {
|
|
|
1241
1261
|
getCurrentVersion: () => getCurrentVersion,
|
|
1242
1262
|
version: () => version
|
|
1243
1263
|
});
|
|
1244
|
-
import { readFileSync as
|
|
1245
|
-
import { fileURLToPath as
|
|
1246
|
-
import { dirname as
|
|
1264
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1265
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1266
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1247
1267
|
import chalk4 from "chalk";
|
|
1248
1268
|
import boxen2 from "boxen";
|
|
1249
1269
|
function getCurrentVersion() {
|
|
1250
1270
|
try {
|
|
1251
|
-
const packageJsonPath2 =
|
|
1252
|
-
const packageJson2 = JSON.parse(
|
|
1271
|
+
const packageJsonPath2 = join6(__dirname5, "../../package.json");
|
|
1272
|
+
const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
|
|
1253
1273
|
return packageJson2.version;
|
|
1254
1274
|
} catch {
|
|
1255
1275
|
logger.debug("Failed to read package.json version");
|
|
@@ -1331,7 +1351,7 @@ function formatVersion(includePrefix = true) {
|
|
|
1331
1351
|
const prefix = includePrefix ? "v" : "";
|
|
1332
1352
|
return `${prefix}${version}`;
|
|
1333
1353
|
}
|
|
1334
|
-
var
|
|
1354
|
+
var __filename5, __dirname5, version;
|
|
1335
1355
|
var init_version = __esm({
|
|
1336
1356
|
"src/utils/version.ts"() {
|
|
1337
1357
|
"use strict";
|
|
@@ -1339,22 +1359,22 @@ var init_version = __esm({
|
|
|
1339
1359
|
init_api_service();
|
|
1340
1360
|
init_storage_service();
|
|
1341
1361
|
init_logger();
|
|
1342
|
-
|
|
1343
|
-
|
|
1362
|
+
__filename5 = fileURLToPath5(import.meta.url);
|
|
1363
|
+
__dirname5 = dirname4(__filename5);
|
|
1344
1364
|
version = getCurrentVersion();
|
|
1345
1365
|
}
|
|
1346
1366
|
});
|
|
1347
1367
|
|
|
1348
1368
|
// src/services/api.service.ts
|
|
1349
|
-
import { ofetch as
|
|
1350
|
-
import { readFileSync as
|
|
1351
|
-
import { fileURLToPath as
|
|
1352
|
-
import { URLSearchParams as
|
|
1353
|
-
import { dirname as
|
|
1369
|
+
import { ofetch as ofetch3 } from "ofetch";
|
|
1370
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1371
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
1372
|
+
import { URLSearchParams as URLSearchParams3 } from "url";
|
|
1373
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1354
1374
|
function getCliVersion() {
|
|
1355
1375
|
try {
|
|
1356
|
-
const packageJsonPath2 =
|
|
1357
|
-
const packageJson2 = JSON.parse(
|
|
1376
|
+
const packageJsonPath2 = join7(__dirname6, "../../package.json");
|
|
1377
|
+
const packageJson2 = JSON.parse(readFileSync5(packageJsonPath2, "utf-8"));
|
|
1358
1378
|
return packageJson2.version;
|
|
1359
1379
|
} catch {
|
|
1360
1380
|
logger.debug("Failed to read package.json version");
|
|
@@ -1366,7 +1386,7 @@ function getPlatformInfo() {
|
|
|
1366
1386
|
const arch = process.arch;
|
|
1367
1387
|
return `${platform}-${arch}`;
|
|
1368
1388
|
}
|
|
1369
|
-
var
|
|
1389
|
+
var __filename6, __dirname6, ApiService, apiService;
|
|
1370
1390
|
var init_api_service = __esm({
|
|
1371
1391
|
"src/services/api.service.ts"() {
|
|
1372
1392
|
"use strict";
|
|
@@ -1375,14 +1395,14 @@ var init_api_service = __esm({
|
|
|
1375
1395
|
init_constants();
|
|
1376
1396
|
init_errors();
|
|
1377
1397
|
init_logger();
|
|
1378
|
-
|
|
1379
|
-
|
|
1398
|
+
__filename6 = fileURLToPath6(import.meta.url);
|
|
1399
|
+
__dirname6 = dirname5(__filename6);
|
|
1380
1400
|
ApiService = class {
|
|
1381
1401
|
baseURL;
|
|
1382
1402
|
client;
|
|
1383
1403
|
constructor() {
|
|
1384
1404
|
this.baseURL = process.env.API_BASE_URL || "https://api.bragduck.com";
|
|
1385
|
-
this.client =
|
|
1405
|
+
this.client = ofetch3.create({
|
|
1386
1406
|
baseURL: this.baseURL,
|
|
1387
1407
|
// Request interceptor
|
|
1388
1408
|
onRequest: async ({ request, options }) => {
|
|
@@ -1546,7 +1566,7 @@ var init_api_service = __esm({
|
|
|
1546
1566
|
const { limit = 50, offset = 0, tags, search } = params;
|
|
1547
1567
|
logger.debug(`Listing brags: limit=${limit}, offset=${offset}`);
|
|
1548
1568
|
try {
|
|
1549
|
-
const queryParams = new
|
|
1569
|
+
const queryParams = new URLSearchParams3({
|
|
1550
1570
|
limit: limit.toString(),
|
|
1551
1571
|
offset: offset.toString()
|
|
1552
1572
|
});
|
|
@@ -1643,7 +1663,7 @@ var init_api_service = __esm({
|
|
|
1643
1663
|
*/
|
|
1644
1664
|
setBaseURL(url) {
|
|
1645
1665
|
this.baseURL = url;
|
|
1646
|
-
this.client =
|
|
1666
|
+
this.client = ofetch3.create({
|
|
1647
1667
|
baseURL: url
|
|
1648
1668
|
});
|
|
1649
1669
|
}
|
|
@@ -1661,18 +1681,266 @@ var init_api_service = __esm({
|
|
|
1661
1681
|
// src/cli.ts
|
|
1662
1682
|
init_esm_shims();
|
|
1663
1683
|
import { Command } from "commander";
|
|
1664
|
-
import { readFileSync as
|
|
1665
|
-
import { fileURLToPath as
|
|
1666
|
-
import { dirname as
|
|
1684
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1685
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
1686
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
1667
1687
|
|
|
1668
1688
|
// src/commands/auth.ts
|
|
1669
1689
|
init_esm_shims();
|
|
1670
1690
|
init_auth_service();
|
|
1671
|
-
init_storage_service();
|
|
1672
1691
|
import boxen from "boxen";
|
|
1673
1692
|
import chalk3 from "chalk";
|
|
1674
1693
|
import { input } from "@inquirer/prompts";
|
|
1675
1694
|
|
|
1695
|
+
// src/services/atlassian-auth.service.ts
|
|
1696
|
+
init_esm_shims();
|
|
1697
|
+
init_constants();
|
|
1698
|
+
init_storage_service();
|
|
1699
|
+
init_oauth_server();
|
|
1700
|
+
init_browser();
|
|
1701
|
+
init_errors();
|
|
1702
|
+
init_logger();
|
|
1703
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1704
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1705
|
+
import { fileURLToPath as fileURLToPath4, URLSearchParams as URLSearchParams2 } from "url";
|
|
1706
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1707
|
+
import { ofetch as ofetch2 } from "ofetch";
|
|
1708
|
+
import { select } from "@inquirer/prompts";
|
|
1709
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1710
|
+
var __dirname4 = dirname3(__filename4);
|
|
1711
|
+
function getUserAgent2() {
|
|
1712
|
+
try {
|
|
1713
|
+
const packageJsonPath2 = join4(__dirname4, "../../package.json");
|
|
1714
|
+
const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
|
|
1715
|
+
const version2 = packageJson2.version;
|
|
1716
|
+
const platform = process.platform;
|
|
1717
|
+
const arch = process.arch;
|
|
1718
|
+
return `BragDuck-CLI/${version2} (${platform}-${arch})`;
|
|
1719
|
+
} catch {
|
|
1720
|
+
logger.debug("Failed to read package.json version");
|
|
1721
|
+
return "BragDuck-CLI/2.0.0";
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
var AtlassianAuthService = class {
|
|
1725
|
+
apiBaseUrl;
|
|
1726
|
+
refreshPromise = null;
|
|
1727
|
+
constructor() {
|
|
1728
|
+
this.apiBaseUrl = process.env.API_BASE_URL || "https://api.bragduck.com";
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Generate a random state string for CSRF protection
|
|
1732
|
+
*/
|
|
1733
|
+
generateState() {
|
|
1734
|
+
return randomBytes3(32).toString("hex");
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Build the Atlassian OAuth authorization URL
|
|
1738
|
+
*/
|
|
1739
|
+
buildAuthUrl(state, callbackUrl) {
|
|
1740
|
+
const params = new URLSearchParams2({
|
|
1741
|
+
audience: ATLASSIAN_OAUTH_CONFIG.AUDIENCE,
|
|
1742
|
+
client_id: ATLASSIAN_OAUTH_CONFIG.CLIENT_ID,
|
|
1743
|
+
scope: ATLASSIAN_OAUTH_CONFIG.SCOPES,
|
|
1744
|
+
redirect_uri: callbackUrl,
|
|
1745
|
+
state,
|
|
1746
|
+
response_type: "code",
|
|
1747
|
+
prompt: "consent"
|
|
1748
|
+
});
|
|
1749
|
+
return `${ATLASSIAN_OAUTH_CONFIG.AUTH_URL}?${params.toString()}`;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Exchange authorization code for tokens via BragDuck backend
|
|
1753
|
+
*/
|
|
1754
|
+
async exchangeCodeForToken(code, redirectUri) {
|
|
1755
|
+
try {
|
|
1756
|
+
logger.debug("Exchanging Atlassian authorization code for token via backend");
|
|
1757
|
+
const response = await ofetch2(
|
|
1758
|
+
`${this.apiBaseUrl}${API_ENDPOINTS.ATLASSIAN.TOKEN}`,
|
|
1759
|
+
{
|
|
1760
|
+
method: "POST",
|
|
1761
|
+
body: {
|
|
1762
|
+
code,
|
|
1763
|
+
redirect_uri: redirectUri
|
|
1764
|
+
},
|
|
1765
|
+
headers: {
|
|
1766
|
+
"Content-Type": "application/json",
|
|
1767
|
+
"User-Agent": getUserAgent2()
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
);
|
|
1771
|
+
logger.debug("Atlassian token exchange successful");
|
|
1772
|
+
return response;
|
|
1773
|
+
} catch (error) {
|
|
1774
|
+
logger.debug(`Atlassian token exchange failed: ${error.message}`);
|
|
1775
|
+
throw new AtlassianError("Failed to exchange Atlassian authorization code", {
|
|
1776
|
+
originalError: error.message
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Fetch accessible Atlassian Cloud resources (sites) using the access token
|
|
1782
|
+
*/
|
|
1783
|
+
async fetchAccessibleResources(accessToken) {
|
|
1784
|
+
try {
|
|
1785
|
+
logger.debug("Fetching accessible Atlassian resources");
|
|
1786
|
+
const resources = await ofetch2(
|
|
1787
|
+
ATLASSIAN_OAUTH_CONFIG.ACCESSIBLE_RESOURCES_URL,
|
|
1788
|
+
{
|
|
1789
|
+
headers: {
|
|
1790
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1791
|
+
Accept: "application/json"
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
);
|
|
1795
|
+
logger.debug(`Found ${resources.length} accessible resource(s)`);
|
|
1796
|
+
return resources;
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
logger.debug(`Failed to fetch accessible resources: ${error.message}`);
|
|
1799
|
+
throw new AtlassianError("Failed to fetch Atlassian Cloud sites", {
|
|
1800
|
+
originalError: error.message
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Let user pick a site if multiple accessible resources exist
|
|
1806
|
+
*/
|
|
1807
|
+
async selectSite(resources) {
|
|
1808
|
+
if (resources.length === 0) {
|
|
1809
|
+
throw new AtlassianError(
|
|
1810
|
+
"No accessible Atlassian Cloud sites found. Make sure your account has access to at least one site."
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
if (resources.length === 1) {
|
|
1814
|
+
const site = resources[0];
|
|
1815
|
+
logger.debug(`Auto-selecting single site: ${site.name}`);
|
|
1816
|
+
return site;
|
|
1817
|
+
}
|
|
1818
|
+
const selectedId = await select({
|
|
1819
|
+
message: "Select your Atlassian Cloud site:",
|
|
1820
|
+
choices: resources.map((r) => ({
|
|
1821
|
+
name: `${r.name} (${r.url})`,
|
|
1822
|
+
value: r.id
|
|
1823
|
+
}))
|
|
1824
|
+
});
|
|
1825
|
+
return resources.find((r) => r.id === selectedId);
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Full OAuth login flow:
|
|
1829
|
+
* 1. Open browser to Atlassian consent screen
|
|
1830
|
+
* 2. Wait for callback with authorization code
|
|
1831
|
+
* 3. Exchange code for tokens via BragDuck backend
|
|
1832
|
+
* 4. Fetch accessible resources and pick site
|
|
1833
|
+
* 5. Store credentials for jira, confluence, and atlassian services
|
|
1834
|
+
*/
|
|
1835
|
+
async login() {
|
|
1836
|
+
logger.debug("Starting Atlassian OAuth login flow");
|
|
1837
|
+
const state = this.generateState();
|
|
1838
|
+
const { callbackUrl, resultPromise } = await startOAuthCallbackServer(state);
|
|
1839
|
+
storageService.setOAuthState({
|
|
1840
|
+
state,
|
|
1841
|
+
createdAt: Date.now()
|
|
1842
|
+
});
|
|
1843
|
+
logger.debug(`OAuth state: ${state}`);
|
|
1844
|
+
logger.debug(`Callback URL: ${callbackUrl}`);
|
|
1845
|
+
const authUrl = this.buildAuthUrl(state, callbackUrl);
|
|
1846
|
+
logger.debug(`Authorization URL: ${authUrl}`);
|
|
1847
|
+
try {
|
|
1848
|
+
await openBrowser(authUrl);
|
|
1849
|
+
} catch {
|
|
1850
|
+
logger.warning("Could not open browser automatically");
|
|
1851
|
+
logger.info("Please open this URL in your browser:");
|
|
1852
|
+
logger.log(authUrl);
|
|
1853
|
+
}
|
|
1854
|
+
let callbackResult;
|
|
1855
|
+
try {
|
|
1856
|
+
callbackResult = await resultPromise;
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
storageService.deleteOAuthState();
|
|
1859
|
+
throw error;
|
|
1860
|
+
}
|
|
1861
|
+
storageService.deleteOAuthState();
|
|
1862
|
+
const tokenResponse = await this.exchangeCodeForToken(callbackResult.code, callbackUrl);
|
|
1863
|
+
const resources = await this.fetchAccessibleResources(tokenResponse.access_token);
|
|
1864
|
+
const site = await this.selectSite(resources);
|
|
1865
|
+
const expiresAt = tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1e3 : void 0;
|
|
1866
|
+
const credentials = {
|
|
1867
|
+
accessToken: tokenResponse.access_token,
|
|
1868
|
+
refreshToken: tokenResponse.refresh_token,
|
|
1869
|
+
expiresAt,
|
|
1870
|
+
instanceUrl: site.url,
|
|
1871
|
+
// Real Cloud URL for browse links
|
|
1872
|
+
cloudId: site.id,
|
|
1873
|
+
authMethod: "oauth"
|
|
1874
|
+
};
|
|
1875
|
+
await storageService.setServiceCredentials("jira", credentials);
|
|
1876
|
+
await storageService.setServiceCredentials("confluence", credentials);
|
|
1877
|
+
await storageService.setServiceCredentials("atlassian", credentials);
|
|
1878
|
+
logger.debug("Atlassian OAuth login successful");
|
|
1879
|
+
return { siteName: site.name, siteUrl: site.url };
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Refresh Atlassian OAuth tokens via BragDuck backend.
|
|
1883
|
+
* Uses a singleton promise to prevent concurrent refresh race conditions
|
|
1884
|
+
* (Atlassian uses rotating refresh tokens - each refresh invalidates the previous one).
|
|
1885
|
+
*/
|
|
1886
|
+
async refreshToken() {
|
|
1887
|
+
if (this.refreshPromise) {
|
|
1888
|
+
return this.refreshPromise;
|
|
1889
|
+
}
|
|
1890
|
+
this.refreshPromise = this.doRefreshToken();
|
|
1891
|
+
try {
|
|
1892
|
+
await this.refreshPromise;
|
|
1893
|
+
} finally {
|
|
1894
|
+
this.refreshPromise = null;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
async doRefreshToken() {
|
|
1898
|
+
logger.debug("Refreshing Atlassian OAuth token");
|
|
1899
|
+
const creds = await storageService.getServiceCredentials("jira");
|
|
1900
|
+
if (!creds?.refreshToken) {
|
|
1901
|
+
throw new AtlassianError("No Atlassian refresh token available", {
|
|
1902
|
+
hint: "Run: bragduck auth atlassian"
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
try {
|
|
1906
|
+
const response = await ofetch2(
|
|
1907
|
+
`${this.apiBaseUrl}${API_ENDPOINTS.ATLASSIAN.REFRESH}`,
|
|
1908
|
+
{
|
|
1909
|
+
method: "POST",
|
|
1910
|
+
body: {
|
|
1911
|
+
refresh_token: creds.refreshToken
|
|
1912
|
+
},
|
|
1913
|
+
headers: {
|
|
1914
|
+
"Content-Type": "application/json",
|
|
1915
|
+
"User-Agent": getUserAgent2()
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
);
|
|
1919
|
+
const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1e3 : void 0;
|
|
1920
|
+
const updatedCreds = {
|
|
1921
|
+
...creds,
|
|
1922
|
+
accessToken: response.access_token,
|
|
1923
|
+
refreshToken: response.refresh_token,
|
|
1924
|
+
expiresAt
|
|
1925
|
+
};
|
|
1926
|
+
await storageService.setServiceCredentials("jira", updatedCreds);
|
|
1927
|
+
await storageService.setServiceCredentials("confluence", updatedCreds);
|
|
1928
|
+
await storageService.setServiceCredentials("atlassian", updatedCreds);
|
|
1929
|
+
logger.debug("Atlassian token refresh successful");
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
logger.debug(`Atlassian token refresh failed: ${error.message}`);
|
|
1932
|
+
throw new AtlassianError(
|
|
1933
|
+
'Atlassian token refresh failed. Please run "bragduck auth atlassian" to re-authenticate.',
|
|
1934
|
+
{ originalError: error.message }
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
var atlassianAuthService = new AtlassianAuthService();
|
|
1940
|
+
|
|
1941
|
+
// src/commands/auth.ts
|
|
1942
|
+
init_storage_service();
|
|
1943
|
+
|
|
1676
1944
|
// src/services/github.service.ts
|
|
1677
1945
|
init_esm_shims();
|
|
1678
1946
|
init_errors();
|
|
@@ -1688,9 +1956,9 @@ import simpleGit from "simple-git";
|
|
|
1688
1956
|
init_esm_shims();
|
|
1689
1957
|
init_errors();
|
|
1690
1958
|
import { existsSync as existsSync2 } from "fs";
|
|
1691
|
-
import { join as
|
|
1959
|
+
import { join as join5 } from "path";
|
|
1692
1960
|
function validateGitRepository(path4) {
|
|
1693
|
-
const gitDir =
|
|
1961
|
+
const gitDir = join5(path4, ".git");
|
|
1694
1962
|
if (!existsSync2(gitDir)) {
|
|
1695
1963
|
throw new GitError(
|
|
1696
1964
|
"Not a git repository. Please run this command from within a git repository.",
|
|
@@ -2404,7 +2672,14 @@ async function authStatus() {
|
|
|
2404
2672
|
}
|
|
2405
2673
|
for (const service of services) {
|
|
2406
2674
|
if (service !== "bragduck") {
|
|
2407
|
-
|
|
2675
|
+
const creds = await storageService.getServiceCredentials(service);
|
|
2676
|
+
let methodLabel = "";
|
|
2677
|
+
if (creds?.authMethod === "oauth") {
|
|
2678
|
+
methodLabel = " (Cloud - OAuth)";
|
|
2679
|
+
} else if (creds?.authMethod === "basic") {
|
|
2680
|
+
methodLabel = " (Server - API Token)";
|
|
2681
|
+
}
|
|
2682
|
+
logger.info(`${colors.success("\u2713")} ${service}: Authenticated${methodLabel}`);
|
|
2408
2683
|
}
|
|
2409
2684
|
}
|
|
2410
2685
|
logger.log("");
|
|
@@ -2556,68 +2831,21 @@ User: ${user.name} (@${user.username})`,
|
|
|
2556
2831
|
}
|
|
2557
2832
|
async function authAtlassian() {
|
|
2558
2833
|
logger.log("");
|
|
2559
|
-
logger.
|
|
2560
|
-
boxen(
|
|
2561
|
-
theme.info("Atlassian API Token Authentication") + "\n\nThis token works for Jira, Confluence, and Bitbucket\n\nCreate an API Token at:\n" + colors.highlight("https://id.atlassian.com/manage-profile/security/api-tokens") + "\n\nRequired access:\n \u2022 Jira: Read issues\n \u2022 Confluence: Read pages\n \u2022 Bitbucket: Read repositories",
|
|
2562
|
-
boxStyles.info
|
|
2563
|
-
)
|
|
2564
|
-
);
|
|
2834
|
+
logger.info("Opening browser for Atlassian Cloud authentication...");
|
|
2565
2835
|
logger.log("");
|
|
2566
2836
|
try {
|
|
2567
|
-
const
|
|
2568
|
-
message: "Atlassian instance URL (e.g., company.atlassian.net):",
|
|
2569
|
-
validate: (value) => value.length > 0 ? true : "Instance URL cannot be empty"
|
|
2570
|
-
});
|
|
2571
|
-
const email = await input({
|
|
2572
|
-
message: "Atlassian account email:",
|
|
2573
|
-
validate: (value) => value.includes("@") ? true : "Please enter a valid email address"
|
|
2574
|
-
});
|
|
2575
|
-
const apiToken = await input({
|
|
2576
|
-
message: "API Token:",
|
|
2577
|
-
validate: (value) => value.length > 0 ? true : "API token cannot be empty"
|
|
2578
|
-
});
|
|
2579
|
-
const testInstanceUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
|
|
2580
|
-
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
2581
|
-
const response = await fetch(`${testInstanceUrl}/rest/api/2/myself`, {
|
|
2582
|
-
headers: { Authorization: `Basic ${auth}` }
|
|
2583
|
-
});
|
|
2584
|
-
if (!response.ok) {
|
|
2585
|
-
logger.log("");
|
|
2586
|
-
logger.log(
|
|
2587
|
-
boxen(
|
|
2588
|
-
theme.error("\u2717 Authentication Failed") + "\n\nInvalid instance URL, email, or API token\nMake sure the instance URL is correct (e.g., company.atlassian.net)",
|
|
2589
|
-
boxStyles.error
|
|
2590
|
-
)
|
|
2591
|
-
);
|
|
2592
|
-
logger.log("");
|
|
2593
|
-
process.exit(1);
|
|
2594
|
-
}
|
|
2595
|
-
const user = await response.json();
|
|
2596
|
-
const credentials = {
|
|
2597
|
-
accessToken: apiToken,
|
|
2598
|
-
username: email,
|
|
2599
|
-
instanceUrl: instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`
|
|
2600
|
-
};
|
|
2601
|
-
await storageService.setServiceCredentials("jira", credentials);
|
|
2602
|
-
await storageService.setServiceCredentials("confluence", credentials);
|
|
2603
|
-
await storageService.setServiceCredentials("bitbucket", {
|
|
2604
|
-
...credentials,
|
|
2605
|
-
instanceUrl: "https://api.bitbucket.org"
|
|
2606
|
-
// Bitbucket uses different API base
|
|
2607
|
-
});
|
|
2837
|
+
const result = await atlassianAuthService.login();
|
|
2608
2838
|
logger.log("");
|
|
2609
2839
|
logger.log(
|
|
2610
2840
|
boxen(
|
|
2611
|
-
theme.success("\u2713 Successfully authenticated with Atlassian") + `
|
|
2841
|
+
theme.success("\u2713 Successfully authenticated with Atlassian Cloud") + `
|
|
2612
2842
|
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
Email: ${user.emailAddress}
|
|
2843
|
+
Site: ${result.siteName}
|
|
2844
|
+
URL: ${result.siteUrl}
|
|
2616
2845
|
|
|
2617
2846
|
Services configured:
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
\u2022 Bitbucket`,
|
|
2847
|
+
- Jira (OAuth)
|
|
2848
|
+
- Confluence (OAuth)`,
|
|
2621
2849
|
boxStyles.success
|
|
2622
2850
|
)
|
|
2623
2851
|
);
|
|
@@ -2639,7 +2867,7 @@ Services configured:
|
|
|
2639
2867
|
// src/commands/sync.ts
|
|
2640
2868
|
init_esm_shims();
|
|
2641
2869
|
|
|
2642
|
-
// node_modules/@inquirer/core/dist/esm/lib/errors.mjs
|
|
2870
|
+
// ../../node_modules/.pnpm/@inquirer+core@9.2.1/node_modules/@inquirer/core/dist/esm/lib/errors.mjs
|
|
2643
2871
|
init_esm_shims();
|
|
2644
2872
|
var CancelPromptError = class extends Error {
|
|
2645
2873
|
name = "CancelPromptError";
|
|
@@ -2650,8 +2878,8 @@ var CancelPromptError = class extends Error {
|
|
|
2650
2878
|
init_api_service();
|
|
2651
2879
|
init_storage_service();
|
|
2652
2880
|
init_auth_service();
|
|
2653
|
-
import { select as
|
|
2654
|
-
import
|
|
2881
|
+
import { select as select4 } from "@inquirer/prompts";
|
|
2882
|
+
import boxen5 from "boxen";
|
|
2655
2883
|
|
|
2656
2884
|
// src/utils/source-detector.ts
|
|
2657
2885
|
init_esm_shims();
|
|
@@ -2659,7 +2887,7 @@ init_errors();
|
|
|
2659
2887
|
init_storage_service();
|
|
2660
2888
|
import { exec as exec3 } from "child_process";
|
|
2661
2889
|
import { promisify as promisify3 } from "util";
|
|
2662
|
-
import { select } from "@inquirer/prompts";
|
|
2890
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
2663
2891
|
var execAsync3 = promisify3(exec3);
|
|
2664
2892
|
var SourceDetector = class {
|
|
2665
2893
|
/**
|
|
@@ -2715,7 +2943,7 @@ var SourceDetector = class {
|
|
|
2715
2943
|
description: source.remoteUrl
|
|
2716
2944
|
};
|
|
2717
2945
|
});
|
|
2718
|
-
return await
|
|
2946
|
+
return await select2({
|
|
2719
2947
|
message: "Multiple sources detected. Which do you want to sync?",
|
|
2720
2948
|
choices
|
|
2721
2949
|
});
|
|
@@ -3231,7 +3459,7 @@ init_logger();
|
|
|
3231
3459
|
init_storage_service();
|
|
3232
3460
|
import { exec as exec5 } from "child_process";
|
|
3233
3461
|
import { promisify as promisify5 } from "util";
|
|
3234
|
-
import { URLSearchParams as
|
|
3462
|
+
import { URLSearchParams as URLSearchParams4 } from "url";
|
|
3235
3463
|
var execAsync5 = promisify5(exec5);
|
|
3236
3464
|
var GitLabService = class {
|
|
3237
3465
|
DEFAULT_INSTANCE = "https://gitlab.com";
|
|
@@ -3377,7 +3605,7 @@ var GitLabService = class {
|
|
|
3377
3605
|
async getMergedMRs(options = {}) {
|
|
3378
3606
|
const { projectPath } = await this.getProjectFromGit();
|
|
3379
3607
|
const encodedPath = encodeURIComponent(projectPath);
|
|
3380
|
-
const params = new
|
|
3608
|
+
const params = new URLSearchParams4({
|
|
3381
3609
|
state: "merged",
|
|
3382
3610
|
order_by: "updated_at",
|
|
3383
3611
|
sort: "desc",
|
|
@@ -3543,41 +3771,76 @@ init_esm_shims();
|
|
|
3543
3771
|
init_errors();
|
|
3544
3772
|
init_logger();
|
|
3545
3773
|
init_storage_service();
|
|
3774
|
+
init_constants();
|
|
3546
3775
|
var JiraService = class {
|
|
3547
3776
|
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
3548
3777
|
/**
|
|
3549
|
-
* Get stored Jira credentials
|
|
3778
|
+
* Get stored Jira credentials, auto-refreshing OAuth tokens if expired
|
|
3550
3779
|
*/
|
|
3551
3780
|
async getCredentials() {
|
|
3552
3781
|
const creds = await storageService.getServiceCredentials("jira");
|
|
3553
|
-
if (!creds || !creds.
|
|
3782
|
+
if (!creds || !creds.accessToken) {
|
|
3554
3783
|
throw new JiraError("Not authenticated with Jira", {
|
|
3555
3784
|
hint: "Run: bragduck auth atlassian"
|
|
3556
3785
|
});
|
|
3557
3786
|
}
|
|
3558
|
-
if (creds.
|
|
3787
|
+
if (creds.authMethod !== "oauth" && (!creds.username || !creds.instanceUrl)) {
|
|
3788
|
+
throw new JiraError("Not authenticated with Jira", {
|
|
3789
|
+
hint: "Run: bragduck auth atlassian"
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
if (creds.authMethod === "oauth" && !creds.cloudId) {
|
|
3793
|
+
throw new JiraError("Missing Atlassian Cloud ID", {
|
|
3794
|
+
hint: "Run: bragduck auth atlassian"
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
if (creds.authMethod === "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
3798
|
+
if (creds.refreshToken) {
|
|
3799
|
+
logger.debug("Jira OAuth token expired, refreshing...");
|
|
3800
|
+
await atlassianAuthService.refreshToken();
|
|
3801
|
+
const refreshed = await storageService.getServiceCredentials("jira");
|
|
3802
|
+
if (!refreshed?.accessToken) {
|
|
3803
|
+
throw new JiraError("Token refresh failed", {
|
|
3804
|
+
hint: "Run: bragduck auth atlassian"
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
return refreshed;
|
|
3808
|
+
}
|
|
3809
|
+
throw new JiraError("OAuth token has expired and no refresh token available", {
|
|
3810
|
+
hint: "Run: bragduck auth atlassian"
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
if (creds.authMethod !== "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
3559
3814
|
throw new JiraError("API token has expired", {
|
|
3560
3815
|
hint: "Run: bragduck auth atlassian"
|
|
3561
3816
|
});
|
|
3562
3817
|
}
|
|
3563
|
-
return
|
|
3564
|
-
email: creds.username,
|
|
3565
|
-
apiToken: creds.accessToken,
|
|
3566
|
-
instanceUrl: creds.instanceUrl
|
|
3567
|
-
};
|
|
3818
|
+
return creds;
|
|
3568
3819
|
}
|
|
3569
3820
|
/**
|
|
3570
|
-
* Make authenticated request to Jira API
|
|
3821
|
+
* Make authenticated request to Jira API.
|
|
3822
|
+
* Uses OAuth Bearer + API gateway for Cloud, Basic Auth for Server/DC.
|
|
3823
|
+
* Retries once on 401 for OAuth (auto-refresh).
|
|
3571
3824
|
*/
|
|
3572
3825
|
async request(endpoint, method = "GET", body) {
|
|
3573
|
-
const
|
|
3574
|
-
const
|
|
3575
|
-
|
|
3826
|
+
const creds = await this.getCredentials();
|
|
3827
|
+
const isOAuth = creds.authMethod === "oauth";
|
|
3828
|
+
let baseUrl;
|
|
3829
|
+
let authHeader;
|
|
3830
|
+
if (isOAuth) {
|
|
3831
|
+
baseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/jira/${creds.cloudId}`;
|
|
3832
|
+
authHeader = `Bearer ${creds.accessToken}`;
|
|
3833
|
+
} else {
|
|
3834
|
+
const instanceUrl = creds.instanceUrl;
|
|
3835
|
+
baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
|
|
3836
|
+
const auth = Buffer.from(`${creds.username}:${creds.accessToken}`).toString("base64");
|
|
3837
|
+
authHeader = `Basic ${auth}`;
|
|
3838
|
+
}
|
|
3576
3839
|
logger.debug(`Jira API: ${method} ${endpoint}`);
|
|
3577
3840
|
const options = {
|
|
3578
3841
|
method,
|
|
3579
3842
|
headers: {
|
|
3580
|
-
Authorization:
|
|
3843
|
+
Authorization: authHeader,
|
|
3581
3844
|
"Content-Type": "application/json",
|
|
3582
3845
|
Accept: "application/json"
|
|
3583
3846
|
}
|
|
@@ -3585,7 +3848,17 @@ var JiraService = class {
|
|
|
3585
3848
|
if (body) {
|
|
3586
3849
|
options.body = JSON.stringify(body);
|
|
3587
3850
|
}
|
|
3588
|
-
|
|
3851
|
+
let response = await fetch(`${baseUrl}${endpoint}`, options);
|
|
3852
|
+
if (response.status === 401 && isOAuth && creds.refreshToken) {
|
|
3853
|
+
logger.debug("Jira OAuth 401, attempting token refresh and retry");
|
|
3854
|
+
await atlassianAuthService.refreshToken();
|
|
3855
|
+
const refreshedCreds = await storageService.getServiceCredentials("jira");
|
|
3856
|
+
if (refreshedCreds?.accessToken) {
|
|
3857
|
+
options.headers.Authorization = `Bearer ${refreshedCreds.accessToken}`;
|
|
3858
|
+
const newBaseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/jira/${refreshedCreds.cloudId}`;
|
|
3859
|
+
response = await fetch(`${newBaseUrl}${endpoint}`, options);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3589
3862
|
if (!response.ok) {
|
|
3590
3863
|
const statusText = response.statusText;
|
|
3591
3864
|
const status = response.status;
|
|
@@ -3991,40 +4264,75 @@ init_esm_shims();
|
|
|
3991
4264
|
init_errors();
|
|
3992
4265
|
init_logger();
|
|
3993
4266
|
init_storage_service();
|
|
4267
|
+
init_constants();
|
|
3994
4268
|
var ConfluenceService = class {
|
|
3995
4269
|
/**
|
|
3996
|
-
* Get stored Confluence credentials
|
|
4270
|
+
* Get stored Confluence credentials, auto-refreshing OAuth tokens if expired
|
|
3997
4271
|
*/
|
|
3998
4272
|
async getCredentials() {
|
|
3999
4273
|
const creds = await storageService.getServiceCredentials("confluence");
|
|
4000
|
-
if (!creds || !creds.
|
|
4274
|
+
if (!creds || !creds.accessToken) {
|
|
4001
4275
|
throw new ConfluenceError("Not authenticated with Confluence", {
|
|
4002
4276
|
hint: "Run: bragduck auth atlassian"
|
|
4003
4277
|
});
|
|
4004
4278
|
}
|
|
4005
|
-
if (creds.
|
|
4279
|
+
if (creds.authMethod !== "oauth" && (!creds.username || !creds.instanceUrl)) {
|
|
4280
|
+
throw new ConfluenceError("Not authenticated with Confluence", {
|
|
4281
|
+
hint: "Run: bragduck auth atlassian"
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
if (creds.authMethod === "oauth" && !creds.cloudId) {
|
|
4285
|
+
throw new ConfluenceError("Missing Atlassian Cloud ID", {
|
|
4286
|
+
hint: "Run: bragduck auth atlassian"
|
|
4287
|
+
});
|
|
4288
|
+
}
|
|
4289
|
+
if (creds.authMethod === "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
4290
|
+
if (creds.refreshToken) {
|
|
4291
|
+
logger.debug("Confluence OAuth token expired, refreshing...");
|
|
4292
|
+
await atlassianAuthService.refreshToken();
|
|
4293
|
+
const refreshed = await storageService.getServiceCredentials("confluence");
|
|
4294
|
+
if (!refreshed?.accessToken) {
|
|
4295
|
+
throw new ConfluenceError("Token refresh failed", {
|
|
4296
|
+
hint: "Run: bragduck auth atlassian"
|
|
4297
|
+
});
|
|
4298
|
+
}
|
|
4299
|
+
return refreshed;
|
|
4300
|
+
}
|
|
4301
|
+
throw new ConfluenceError("OAuth token has expired and no refresh token available", {
|
|
4302
|
+
hint: "Run: bragduck auth atlassian"
|
|
4303
|
+
});
|
|
4304
|
+
}
|
|
4305
|
+
if (creds.authMethod !== "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
4006
4306
|
throw new ConfluenceError("API token has expired", {
|
|
4007
4307
|
hint: "Run: bragduck auth atlassian"
|
|
4008
4308
|
});
|
|
4009
4309
|
}
|
|
4010
|
-
return
|
|
4011
|
-
email: creds.username,
|
|
4012
|
-
apiToken: creds.accessToken,
|
|
4013
|
-
instanceUrl: creds.instanceUrl
|
|
4014
|
-
};
|
|
4310
|
+
return creds;
|
|
4015
4311
|
}
|
|
4016
4312
|
/**
|
|
4017
|
-
* Make authenticated request to Confluence API
|
|
4313
|
+
* Make authenticated request to Confluence API.
|
|
4314
|
+
* Uses OAuth Bearer + API gateway for Cloud, Basic Auth for Server/DC.
|
|
4315
|
+
* Retries once on 401 for OAuth (auto-refresh).
|
|
4018
4316
|
*/
|
|
4019
4317
|
async request(endpoint, method = "GET", body) {
|
|
4020
|
-
const
|
|
4021
|
-
const
|
|
4022
|
-
|
|
4318
|
+
const creds = await this.getCredentials();
|
|
4319
|
+
const isOAuth = creds.authMethod === "oauth";
|
|
4320
|
+
let baseUrl;
|
|
4321
|
+
let authHeader;
|
|
4322
|
+
if (isOAuth) {
|
|
4323
|
+
baseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/confluence/${creds.cloudId}`;
|
|
4324
|
+
authHeader = `Bearer ${creds.accessToken}`;
|
|
4325
|
+
} else {
|
|
4326
|
+
const instanceUrl = creds.instanceUrl;
|
|
4327
|
+
baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
|
|
4328
|
+
const auth = Buffer.from(`${creds.username}:${creds.accessToken}`).toString("base64");
|
|
4329
|
+
authHeader = `Basic ${auth}`;
|
|
4330
|
+
}
|
|
4023
4331
|
logger.debug(`Confluence API: ${method} ${endpoint}`);
|
|
4024
4332
|
const options = {
|
|
4025
4333
|
method,
|
|
4026
4334
|
headers: {
|
|
4027
|
-
Authorization:
|
|
4335
|
+
Authorization: authHeader,
|
|
4028
4336
|
"Content-Type": "application/json",
|
|
4029
4337
|
Accept: "application/json"
|
|
4030
4338
|
}
|
|
@@ -4032,7 +4340,17 @@ var ConfluenceService = class {
|
|
|
4032
4340
|
if (body) {
|
|
4033
4341
|
options.body = JSON.stringify(body);
|
|
4034
4342
|
}
|
|
4035
|
-
|
|
4343
|
+
let response = await fetch(`${baseUrl}${endpoint}`, options);
|
|
4344
|
+
if (response.status === 401 && isOAuth && creds.refreshToken) {
|
|
4345
|
+
logger.debug("Confluence OAuth 401, attempting token refresh and retry");
|
|
4346
|
+
await atlassianAuthService.refreshToken();
|
|
4347
|
+
const refreshedCreds = await storageService.getServiceCredentials("confluence");
|
|
4348
|
+
if (refreshedCreds?.accessToken) {
|
|
4349
|
+
options.headers.Authorization = `Bearer ${refreshedCreds.accessToken}`;
|
|
4350
|
+
const newBaseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/confluence/${refreshedCreds.cloudId}`;
|
|
4351
|
+
response = await fetch(`${newBaseUrl}${endpoint}`, options);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4036
4354
|
if (!response.ok) {
|
|
4037
4355
|
const statusText = response.statusText;
|
|
4038
4356
|
const status = response.status;
|
|
@@ -4441,138 +4759,13 @@ init_logger();
|
|
|
4441
4759
|
// src/utils/auth-helper.ts
|
|
4442
4760
|
init_esm_shims();
|
|
4443
4761
|
init_auth_service();
|
|
4444
|
-
import
|
|
4762
|
+
import boxen4 from "boxen";
|
|
4763
|
+
init_logger();
|
|
4445
4764
|
|
|
4446
|
-
// src/
|
|
4765
|
+
// src/ui/prompts.ts
|
|
4447
4766
|
init_esm_shims();
|
|
4448
|
-
|
|
4449
|
-
init_logger();
|
|
4450
|
-
import ora from "ora";
|
|
4767
|
+
import { checkbox, confirm, input as input2, select as select3, editor } from "@inquirer/prompts";
|
|
4451
4768
|
import boxen3 from "boxen";
|
|
4452
|
-
import chalk5 from "chalk";
|
|
4453
|
-
async function initCommand() {
|
|
4454
|
-
logger.log("");
|
|
4455
|
-
logger.log(
|
|
4456
|
-
boxen3(
|
|
4457
|
-
chalk5.yellow.bold("\u26A0 Deprecation Notice") + `
|
|
4458
|
-
|
|
4459
|
-
The ${chalk5.cyan("init")} command is deprecated.
|
|
4460
|
-
Please use ${chalk5.cyan("bragduck auth login")} instead.
|
|
4461
|
-
|
|
4462
|
-
` + chalk5.dim("This command will be removed in v3.0.0"),
|
|
4463
|
-
{
|
|
4464
|
-
padding: 1,
|
|
4465
|
-
borderStyle: "round",
|
|
4466
|
-
borderColor: "yellow",
|
|
4467
|
-
dimBorder: true
|
|
4468
|
-
}
|
|
4469
|
-
)
|
|
4470
|
-
);
|
|
4471
|
-
logger.log("");
|
|
4472
|
-
logger.info("Starting authentication flow...");
|
|
4473
|
-
logger.log("");
|
|
4474
|
-
const isAuthenticated = await authService.isAuthenticated();
|
|
4475
|
-
if (isAuthenticated) {
|
|
4476
|
-
const userInfo = authService.getUserInfo();
|
|
4477
|
-
if (userInfo) {
|
|
4478
|
-
logger.log(
|
|
4479
|
-
boxen3(
|
|
4480
|
-
`${chalk5.yellow("Already authenticated!")}
|
|
4481
|
-
|
|
4482
|
-
${chalk5.gray("User:")} ${userInfo.name}
|
|
4483
|
-
${chalk5.gray("Email:")} ${userInfo.email}
|
|
4484
|
-
|
|
4485
|
-
${chalk5.dim("Run")} ${chalk5.cyan("bragduck logout")} ${chalk5.dim("to sign out")}`,
|
|
4486
|
-
{
|
|
4487
|
-
padding: 1,
|
|
4488
|
-
margin: 1,
|
|
4489
|
-
borderStyle: "round",
|
|
4490
|
-
borderColor: "yellow"
|
|
4491
|
-
}
|
|
4492
|
-
)
|
|
4493
|
-
);
|
|
4494
|
-
logger.log("");
|
|
4495
|
-
globalThis.setTimeout(() => {
|
|
4496
|
-
process.exit(0);
|
|
4497
|
-
}, 100);
|
|
4498
|
-
return;
|
|
4499
|
-
}
|
|
4500
|
-
}
|
|
4501
|
-
const spinner = ora("Opening browser for authentication...").start();
|
|
4502
|
-
try {
|
|
4503
|
-
spinner.text = "Waiting for authentication...";
|
|
4504
|
-
const userInfo = await authService.login();
|
|
4505
|
-
spinner.succeed("Authentication successful!");
|
|
4506
|
-
logger.log("");
|
|
4507
|
-
logger.log(
|
|
4508
|
-
boxen3(
|
|
4509
|
-
`${chalk5.green.bold("\u2713 Successfully authenticated!")}
|
|
4510
|
-
|
|
4511
|
-
${chalk5.gray("Welcome,")} ${chalk5.cyan(userInfo.name)}
|
|
4512
|
-
${chalk5.gray("Email:")} ${userInfo.email}
|
|
4513
|
-
|
|
4514
|
-
${chalk5.dim("You can now use")} ${chalk5.cyan("bragduck scan")} ${chalk5.dim("to create brags!")}`,
|
|
4515
|
-
{
|
|
4516
|
-
padding: 1,
|
|
4517
|
-
margin: 1,
|
|
4518
|
-
borderStyle: "round",
|
|
4519
|
-
borderColor: "green"
|
|
4520
|
-
}
|
|
4521
|
-
)
|
|
4522
|
-
);
|
|
4523
|
-
logger.log("");
|
|
4524
|
-
globalThis.setTimeout(() => {
|
|
4525
|
-
process.exit(0);
|
|
4526
|
-
}, 100);
|
|
4527
|
-
return;
|
|
4528
|
-
} catch (error) {
|
|
4529
|
-
spinner.fail("Authentication failed");
|
|
4530
|
-
logger.log("");
|
|
4531
|
-
const err = error;
|
|
4532
|
-
logger.log(
|
|
4533
|
-
boxen3(
|
|
4534
|
-
`${chalk5.red.bold("\u2717 Authentication Failed")}
|
|
4535
|
-
|
|
4536
|
-
${err.message}
|
|
4537
|
-
|
|
4538
|
-
${chalk5.dim("Hint:")} ${getErrorHint(err)}`,
|
|
4539
|
-
{
|
|
4540
|
-
padding: 1,
|
|
4541
|
-
margin: 1,
|
|
4542
|
-
borderStyle: "round",
|
|
4543
|
-
borderColor: "red"
|
|
4544
|
-
}
|
|
4545
|
-
)
|
|
4546
|
-
);
|
|
4547
|
-
process.exit(1);
|
|
4548
|
-
}
|
|
4549
|
-
}
|
|
4550
|
-
function getErrorHint(error) {
|
|
4551
|
-
if (error.name === "OAuthError") {
|
|
4552
|
-
if (error.message.includes("timeout")) {
|
|
4553
|
-
return "Try again and complete the authentication within 2 minutes";
|
|
4554
|
-
}
|
|
4555
|
-
if (error.message.includes("CSRF")) {
|
|
4556
|
-
return "This might be a security issue. Try running the command again";
|
|
4557
|
-
}
|
|
4558
|
-
return "Check your internet connection and try again";
|
|
4559
|
-
}
|
|
4560
|
-
if (error.name === "NetworkError") {
|
|
4561
|
-
return "Check your internet connection and firewall settings";
|
|
4562
|
-
}
|
|
4563
|
-
if (error.name === "AuthenticationError") {
|
|
4564
|
-
return "Verify your credentials and try again";
|
|
4565
|
-
}
|
|
4566
|
-
return "Try running the command again or check the logs with DEBUG=* bragduck init";
|
|
4567
|
-
}
|
|
4568
|
-
|
|
4569
|
-
// src/utils/auth-helper.ts
|
|
4570
|
-
init_logger();
|
|
4571
|
-
|
|
4572
|
-
// src/ui/prompts.ts
|
|
4573
|
-
init_esm_shims();
|
|
4574
|
-
import { checkbox, confirm, input as input2, select as select2, editor } from "@inquirer/prompts";
|
|
4575
|
-
import boxen4 from "boxen";
|
|
4576
4769
|
|
|
4577
4770
|
// src/ui/formatters.ts
|
|
4578
4771
|
init_esm_shims();
|
|
@@ -4661,7 +4854,13 @@ function formatRefinedCommitsTable(brags, selectedCommits) {
|
|
|
4661
4854
|
} else {
|
|
4662
4855
|
displaySha = `#${index + 1}`;
|
|
4663
4856
|
}
|
|
4664
|
-
|
|
4857
|
+
let original;
|
|
4858
|
+
if (isNewBragType) {
|
|
4859
|
+
const input3 = brag.original_input;
|
|
4860
|
+
original = typeof input3 === "string" ? input3.split("\n")[0] || "" : "Raw input";
|
|
4861
|
+
} else {
|
|
4862
|
+
original = brag.original_message.split("\n")[0] || "";
|
|
4863
|
+
}
|
|
4665
4864
|
const title = brag.refined_title;
|
|
4666
4865
|
const description = brag.refined_description.length > 100 ? brag.refined_description.substring(0, 97) + "..." : brag.refined_description;
|
|
4667
4866
|
const tags = (brag.suggested_tags || []).join(", ") || "none";
|
|
@@ -4786,7 +4985,7 @@ async function promptDaysToScan(defaultDays = 30) {
|
|
|
4786
4985
|
{ name: "90 days", value: "90", description: "Last 3 months" },
|
|
4787
4986
|
{ name: "Custom", value: "custom", description: "Enter custom number of days" }
|
|
4788
4987
|
];
|
|
4789
|
-
const selected = await
|
|
4988
|
+
const selected = await select3({
|
|
4790
4989
|
message: "How many days back should we scan for PRs?",
|
|
4791
4990
|
choices,
|
|
4792
4991
|
default: "30"
|
|
@@ -4820,7 +5019,7 @@ async function promptScanMode() {
|
|
|
4820
5019
|
description: "Scan git commits directly"
|
|
4821
5020
|
}
|
|
4822
5021
|
];
|
|
4823
|
-
return await
|
|
5022
|
+
return await select3({
|
|
4824
5023
|
message: "What would you like to scan?",
|
|
4825
5024
|
choices,
|
|
4826
5025
|
default: "prs"
|
|
@@ -4837,7 +5036,7 @@ async function promptSortOption() {
|
|
|
4837
5036
|
{ name: "By files (most files)", value: "files", description: "Most files changed" },
|
|
4838
5037
|
{ name: "No sorting", value: "none", description: "Keep original order" }
|
|
4839
5038
|
];
|
|
4840
|
-
return await
|
|
5039
|
+
return await select3({
|
|
4841
5040
|
message: "How would you like to sort the PRs?",
|
|
4842
5041
|
choices,
|
|
4843
5042
|
default: "date"
|
|
@@ -4851,7 +5050,7 @@ async function promptSelectOrganisation(organisations) {
|
|
|
4851
5050
|
value: org.id
|
|
4852
5051
|
}))
|
|
4853
5052
|
];
|
|
4854
|
-
const selected = await
|
|
5053
|
+
const selected = await select3({
|
|
4855
5054
|
message: "Attach brags to which company?",
|
|
4856
5055
|
choices,
|
|
4857
5056
|
default: "none"
|
|
@@ -4914,9 +5113,9 @@ ${theme.label("Impact Score")} ${colors.highlight(impactScore)}`;
|
|
|
4914
5113
|
|
|
4915
5114
|
${theme.label("PR Link")} ${colors.link(prUrl)}`;
|
|
4916
5115
|
}
|
|
4917
|
-
console.log(
|
|
5116
|
+
console.log(boxen3(bragDetails, boxStyles.info));
|
|
4918
5117
|
console.log("");
|
|
4919
|
-
const action = await
|
|
5118
|
+
const action = await select3({
|
|
4920
5119
|
message: `What would you like to do with this brag?`,
|
|
4921
5120
|
choices: [
|
|
4922
5121
|
{ name: "\u2713 Accept", value: "accept", description: "Add this brag as-is" },
|
|
@@ -4991,7 +5190,7 @@ async function ensureAuthenticated() {
|
|
|
4991
5190
|
}
|
|
4992
5191
|
logger.log("");
|
|
4993
5192
|
logger.log(
|
|
4994
|
-
|
|
5193
|
+
boxen4(
|
|
4995
5194
|
theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
|
|
4996
5195
|
boxStyles.warning
|
|
4997
5196
|
)
|
|
@@ -5007,7 +5206,7 @@ async function ensureAuthenticated() {
|
|
|
5007
5206
|
return false;
|
|
5008
5207
|
}
|
|
5009
5208
|
try {
|
|
5010
|
-
await
|
|
5209
|
+
await authCommand("login");
|
|
5011
5210
|
return true;
|
|
5012
5211
|
} catch {
|
|
5013
5212
|
return false;
|
|
@@ -5016,9 +5215,9 @@ async function ensureAuthenticated() {
|
|
|
5016
5215
|
|
|
5017
5216
|
// src/ui/spinners.ts
|
|
5018
5217
|
init_esm_shims();
|
|
5019
|
-
import
|
|
5218
|
+
import ora from "ora";
|
|
5020
5219
|
function createSpinner(text) {
|
|
5021
|
-
return
|
|
5220
|
+
return ora({
|
|
5022
5221
|
text,
|
|
5023
5222
|
color: "cyan",
|
|
5024
5223
|
spinner: "dots"
|
|
@@ -5026,7 +5225,7 @@ function createSpinner(text) {
|
|
|
5026
5225
|
}
|
|
5027
5226
|
function createStepSpinner(currentStep, totalSteps, text) {
|
|
5028
5227
|
const stepIndicator = theme.step(currentStep, totalSteps);
|
|
5029
|
-
return
|
|
5228
|
+
return ora({
|
|
5030
5229
|
text: `${stepIndicator} ${text}`,
|
|
5031
5230
|
color: "cyan",
|
|
5032
5231
|
spinner: "dots"
|
|
@@ -5329,7 +5528,10 @@ async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, opt
|
|
|
5329
5528
|
repository: repoInfo.url,
|
|
5330
5529
|
date: originalCommit?.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
5331
5530
|
commit_url: originalCommit?.url || "",
|
|
5531
|
+
impactLevel: refined.suggested_impactLevel,
|
|
5532
|
+
typeId: refined.suggested_typeId,
|
|
5332
5533
|
impact_score: refined.suggested_impactLevel,
|
|
5534
|
+
impactDescription: refined.impact_description,
|
|
5333
5535
|
impact_description: refined.impact_description,
|
|
5334
5536
|
attachments: originalCommit?.url ? [originalCommit.url] : [],
|
|
5335
5537
|
orgId: orgId || void 0,
|
|
@@ -5533,7 +5735,7 @@ async function promptSelectService() {
|
|
|
5533
5735
|
description: `Sync all ${authenticatedSyncServices.length} authenticated service${authenticatedSyncServices.length > 1 ? "s" : ""}`
|
|
5534
5736
|
});
|
|
5535
5737
|
}
|
|
5536
|
-
const selected = await
|
|
5738
|
+
const selected = await select4({
|
|
5537
5739
|
message: "Select a service to sync:",
|
|
5538
5740
|
choices: serviceChoices
|
|
5539
5741
|
});
|
|
@@ -5739,7 +5941,10 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
|
|
|
5739
5941
|
repository: repoInfo.url,
|
|
5740
5942
|
date: originalCommit?.date || (/* @__PURE__ */ new Date()).toISOString(),
|
|
5741
5943
|
commit_url: originalCommit?.url || "",
|
|
5944
|
+
impactLevel: refined.suggested_impactLevel,
|
|
5945
|
+
typeId: refined.suggested_typeId,
|
|
5742
5946
|
impact_score: refined.suggested_impactLevel,
|
|
5947
|
+
impactDescription: refined.impact_description,
|
|
5743
5948
|
impact_description: refined.impact_description,
|
|
5744
5949
|
attachments: originalCommit?.url ? [originalCommit.url] : [],
|
|
5745
5950
|
orgId: selectedOrgId || void 0,
|
|
@@ -5943,10 +6148,10 @@ async function syncAllAuthenticatedServices(options) {
|
|
|
5943
6148
|
}
|
|
5944
6149
|
if (totalCreated > 0) {
|
|
5945
6150
|
const allCreatedBrags = results.filter((r) => r.createdBrags).flatMap((r) => r.createdBrags);
|
|
5946
|
-
logger.log(
|
|
6151
|
+
logger.log(boxen5(formatSuccessMessage(totalCreated, allCreatedBrags), boxStyles.success));
|
|
5947
6152
|
} else if (successful.length > 0) {
|
|
5948
6153
|
logger.log(
|
|
5949
|
-
|
|
6154
|
+
boxen5(
|
|
5950
6155
|
theme.secondary("No new brags created (all items already exist or were skipped)"),
|
|
5951
6156
|
boxStyles.info
|
|
5952
6157
|
)
|
|
@@ -5970,7 +6175,7 @@ async function syncCommand(options = {}) {
|
|
|
5970
6175
|
logger.debug("FREE tier detected - blocking sync command");
|
|
5971
6176
|
logger.log("");
|
|
5972
6177
|
logger.log(
|
|
5973
|
-
|
|
6178
|
+
boxen5(
|
|
5974
6179
|
theme.warning("CLI Access Requires Subscription") + "\n\nThe Bragduck CLI is available for Plus and Pro subscribers.\nUpgrade now to unlock:\n\n \u2022 Automatic work item scanning\n \u2022 AI-powered brag generation\n \u2022 Unlimited brags\n\n" + colors.highlight("Start your free trial today"),
|
|
5975
6180
|
{
|
|
5976
6181
|
...boxStyles.warning,
|
|
@@ -6029,7 +6234,7 @@ async function syncCommand(options = {}) {
|
|
|
6029
6234
|
const result2 = await syncMultipleRepositories(repos, options);
|
|
6030
6235
|
if (result2.totalCreated > 0 || result2.totalSkipped > 0) {
|
|
6031
6236
|
logger.log("");
|
|
6032
|
-
logger.log(
|
|
6237
|
+
logger.log(boxen5(formatMultiRepoSummary(result2), boxStyles.success));
|
|
6033
6238
|
} else {
|
|
6034
6239
|
logger.log("");
|
|
6035
6240
|
logger.info("No brags created.");
|
|
@@ -6062,7 +6267,7 @@ async function syncCommand(options = {}) {
|
|
|
6062
6267
|
const result = await syncSingleService(sourceType, options, TOTAL_STEPS);
|
|
6063
6268
|
if (result.created > 0) {
|
|
6064
6269
|
logger.log(
|
|
6065
|
-
|
|
6270
|
+
boxen5(formatSuccessMessage(result.created, result.createdBrags), boxStyles.success)
|
|
6066
6271
|
);
|
|
6067
6272
|
} else if (result.skipped > 0) {
|
|
6068
6273
|
logger.log("");
|
|
@@ -6089,12 +6294,12 @@ async function syncCommand(options = {}) {
|
|
|
6089
6294
|
const err = error;
|
|
6090
6295
|
logger.log("");
|
|
6091
6296
|
logger.log(
|
|
6092
|
-
|
|
6297
|
+
boxen5(formatErrorMessage(err.message, getErrorHint(err, sourceType)), boxStyles.error)
|
|
6093
6298
|
);
|
|
6094
6299
|
process.exit(1);
|
|
6095
6300
|
}
|
|
6096
6301
|
}
|
|
6097
|
-
function
|
|
6302
|
+
function getErrorHint(error, sourceType) {
|
|
6098
6303
|
if (error.name === "GitHubError") {
|
|
6099
6304
|
return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
|
|
6100
6305
|
}
|
|
@@ -6121,17 +6326,17 @@ init_esm_shims();
|
|
|
6121
6326
|
init_auth_service();
|
|
6122
6327
|
init_logger();
|
|
6123
6328
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
6124
|
-
import
|
|
6125
|
-
import
|
|
6126
|
-
import
|
|
6329
|
+
import boxen6 from "boxen";
|
|
6330
|
+
import chalk5 from "chalk";
|
|
6331
|
+
import ora2 from "ora";
|
|
6127
6332
|
async function logoutCommand() {
|
|
6128
6333
|
const isAuthenticated = await authService.isAuthenticated();
|
|
6129
6334
|
if (!isAuthenticated) {
|
|
6130
6335
|
logger.log(
|
|
6131
|
-
|
|
6132
|
-
`${
|
|
6336
|
+
boxen6(
|
|
6337
|
+
`${chalk5.yellow("Not currently authenticated")}
|
|
6133
6338
|
|
|
6134
|
-
${
|
|
6339
|
+
${chalk5.dim("Nothing to logout from")}`,
|
|
6135
6340
|
{
|
|
6136
6341
|
padding: 1,
|
|
6137
6342
|
margin: 1,
|
|
@@ -6146,25 +6351,25 @@ ${chalk6.dim("Nothing to logout from")}`,
|
|
|
6146
6351
|
const userName = userInfo?.name || "Unknown User";
|
|
6147
6352
|
logger.log("");
|
|
6148
6353
|
const shouldLogout = await confirm2({
|
|
6149
|
-
message: `Are you sure you want to logout? (${
|
|
6354
|
+
message: `Are you sure you want to logout? (${chalk5.cyan(userName)})`,
|
|
6150
6355
|
default: false
|
|
6151
6356
|
});
|
|
6152
6357
|
if (!shouldLogout) {
|
|
6153
6358
|
logger.info("Logout cancelled");
|
|
6154
6359
|
return;
|
|
6155
6360
|
}
|
|
6156
|
-
const spinner =
|
|
6361
|
+
const spinner = ora2("Logging out...").start();
|
|
6157
6362
|
try {
|
|
6158
6363
|
await authService.logout();
|
|
6159
6364
|
spinner.succeed("Logged out successfully");
|
|
6160
6365
|
logger.log("");
|
|
6161
6366
|
logger.log(
|
|
6162
|
-
|
|
6163
|
-
`${
|
|
6367
|
+
boxen6(
|
|
6368
|
+
`${chalk5.green.bold("\u2713 Logged out successfully")}
|
|
6164
6369
|
|
|
6165
|
-
${
|
|
6370
|
+
${chalk5.dim("Your credentials have been cleared")}
|
|
6166
6371
|
|
|
6167
|
-
${
|
|
6372
|
+
${chalk5.dim("Run")} ${chalk5.cyan("bragduck init")} ${chalk5.dim("to login again")}`,
|
|
6168
6373
|
{
|
|
6169
6374
|
padding: 1,
|
|
6170
6375
|
margin: 1,
|
|
@@ -6180,293 +6385,10 @@ ${chalk6.dim("Run")} ${chalk6.cyan("bragduck init")} ${chalk6.dim("to login agai
|
|
|
6180
6385
|
}
|
|
6181
6386
|
}
|
|
6182
6387
|
|
|
6183
|
-
// src/commands/scan.ts
|
|
6184
|
-
init_esm_shims();
|
|
6185
|
-
import boxen8 from "boxen";
|
|
6186
|
-
import chalk7 from "chalk";
|
|
6187
|
-
init_api_service();
|
|
6188
|
-
init_storage_service();
|
|
6189
|
-
init_logger();
|
|
6190
|
-
init_browser();
|
|
6191
|
-
init_auth_service();
|
|
6192
|
-
async function scanCommand(options = {}) {
|
|
6193
|
-
logger.log("");
|
|
6194
|
-
logger.log(
|
|
6195
|
-
boxen8(
|
|
6196
|
-
chalk7.yellow.bold("\u26A0 Deprecation Notice") + `
|
|
6197
|
-
|
|
6198
|
-
The ${chalk7.cyan("scan")} command is deprecated.
|
|
6199
|
-
Please use ${chalk7.cyan("bragduck sync")} instead.
|
|
6200
|
-
|
|
6201
|
-
` + chalk7.dim("This command will be removed in v3.0.0"),
|
|
6202
|
-
{
|
|
6203
|
-
padding: 1,
|
|
6204
|
-
borderStyle: "round",
|
|
6205
|
-
borderColor: "yellow",
|
|
6206
|
-
dimBorder: true
|
|
6207
|
-
}
|
|
6208
|
-
)
|
|
6209
|
-
);
|
|
6210
|
-
logger.log("");
|
|
6211
|
-
const TOTAL_STEPS = 5;
|
|
6212
|
-
try {
|
|
6213
|
-
const isAuthenticated = await ensureAuthenticated();
|
|
6214
|
-
if (!isAuthenticated) {
|
|
6215
|
-
process.exit(1);
|
|
6216
|
-
}
|
|
6217
|
-
logger.debug("Fetching subscription status...");
|
|
6218
|
-
const subscriptionStatus = await apiService.getSubscriptionStatus();
|
|
6219
|
-
logger.debug("Subscription status response:", JSON.stringify(subscriptionStatus, null, 2));
|
|
6220
|
-
logger.debug(
|
|
6221
|
-
`Checking tier: "${subscriptionStatus.tier}" (type: ${typeof subscriptionStatus.tier})`
|
|
6222
|
-
);
|
|
6223
|
-
logger.debug(`Tier === 'FREE': ${subscriptionStatus.tier === "FREE"}`);
|
|
6224
|
-
if (subscriptionStatus.tier === "FREE") {
|
|
6225
|
-
logger.debug("FREE tier detected - blocking scan command");
|
|
6226
|
-
logger.log("");
|
|
6227
|
-
logger.log(
|
|
6228
|
-
boxen8(
|
|
6229
|
-
theme.warning("CLI Access Requires Subscription") + "\n\nThe Bragduck CLI is available for Plus and Pro subscribers.\nUpgrade now to unlock:\n\n \u2022 Automatic PR scanning\n \u2022 AI-powered brag generation\n \u2022 Unlimited brags\n\n" + colors.highlight("Start your free trial today!"),
|
|
6230
|
-
{
|
|
6231
|
-
...boxStyles.warning,
|
|
6232
|
-
padding: 1,
|
|
6233
|
-
margin: 1
|
|
6234
|
-
}
|
|
6235
|
-
)
|
|
6236
|
-
);
|
|
6237
|
-
logger.log("");
|
|
6238
|
-
const shouldOpenBrowser = await promptConfirm(
|
|
6239
|
-
"Open subscription plans in your browser?",
|
|
6240
|
-
true
|
|
6241
|
-
);
|
|
6242
|
-
if (shouldOpenBrowser) {
|
|
6243
|
-
logger.log("");
|
|
6244
|
-
const plansUrl = "https://bragduck.com/app/settings/plans";
|
|
6245
|
-
try {
|
|
6246
|
-
await openBrowser(plansUrl);
|
|
6247
|
-
logger.info(theme.secondary("Opening browser..."));
|
|
6248
|
-
} catch {
|
|
6249
|
-
logger.warning("Could not open browser automatically");
|
|
6250
|
-
logger.info(`Please visit: ${theme.value(plansUrl)}`);
|
|
6251
|
-
}
|
|
6252
|
-
}
|
|
6253
|
-
logger.log("");
|
|
6254
|
-
return;
|
|
6255
|
-
}
|
|
6256
|
-
logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with scan`);
|
|
6257
|
-
const repoSpinner = createStepSpinner(1, TOTAL_STEPS, "Validating GitHub repository");
|
|
6258
|
-
repoSpinner.start();
|
|
6259
|
-
await githubService.validateGitHubRepository();
|
|
6260
|
-
const repoInfo = await githubService.getRepositoryInfo();
|
|
6261
|
-
succeedStepSpinner(
|
|
6262
|
-
repoSpinner,
|
|
6263
|
-
1,
|
|
6264
|
-
TOTAL_STEPS,
|
|
6265
|
-
`Repository: ${theme.value(repoInfo.fullName)}`
|
|
6266
|
-
);
|
|
6267
|
-
logger.log("");
|
|
6268
|
-
let days = options.days;
|
|
6269
|
-
if (!days) {
|
|
6270
|
-
const defaultDays = storageService.getConfig("defaultCommitDays");
|
|
6271
|
-
days = await promptDaysToScan(defaultDays);
|
|
6272
|
-
logger.log("");
|
|
6273
|
-
}
|
|
6274
|
-
const prSpinner = createStepSpinner(
|
|
6275
|
-
2,
|
|
6276
|
-
TOTAL_STEPS,
|
|
6277
|
-
`Fetching merged PRs from the last ${days} days`
|
|
6278
|
-
);
|
|
6279
|
-
prSpinner.start();
|
|
6280
|
-
let prs;
|
|
6281
|
-
if (options.all) {
|
|
6282
|
-
prs = await githubService.getMergedPRs({ days });
|
|
6283
|
-
} else {
|
|
6284
|
-
prs = await githubService.getPRsByCurrentUser({ days });
|
|
6285
|
-
}
|
|
6286
|
-
const commits = prs.map((pr) => githubService.transformPRToCommit(pr));
|
|
6287
|
-
if (commits.length === 0) {
|
|
6288
|
-
failStepSpinner(prSpinner, 2, TOTAL_STEPS, `No merged PRs found in the last ${days} days`);
|
|
6289
|
-
logger.log("");
|
|
6290
|
-
logger.info("Try increasing the number of days or check your GitHub activity");
|
|
6291
|
-
return;
|
|
6292
|
-
}
|
|
6293
|
-
succeedStepSpinner(
|
|
6294
|
-
prSpinner,
|
|
6295
|
-
2,
|
|
6296
|
-
TOTAL_STEPS,
|
|
6297
|
-
`Found ${theme.count(commits.length)} PR${commits.length > 1 ? "s" : ""}`
|
|
6298
|
-
);
|
|
6299
|
-
logger.log("");
|
|
6300
|
-
logger.log(formatCommitStats(commits));
|
|
6301
|
-
logger.log("");
|
|
6302
|
-
let sortedCommits = [...commits];
|
|
6303
|
-
if (commits.length > 1) {
|
|
6304
|
-
const sortOption = await promptSortOption();
|
|
6305
|
-
logger.log("");
|
|
6306
|
-
if (sortOption === "date") {
|
|
6307
|
-
sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
6308
|
-
} else if (sortOption === "size") {
|
|
6309
|
-
sortedCommits.sort((a, b) => {
|
|
6310
|
-
const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
|
|
6311
|
-
const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
|
|
6312
|
-
return sizeB - sizeA;
|
|
6313
|
-
});
|
|
6314
|
-
} else if (sortOption === "files") {
|
|
6315
|
-
sortedCommits.sort((a, b) => {
|
|
6316
|
-
const filesA = a.diffStats?.filesChanged || 0;
|
|
6317
|
-
const filesB = b.diffStats?.filesChanged || 0;
|
|
6318
|
-
return filesB - filesA;
|
|
6319
|
-
});
|
|
6320
|
-
}
|
|
6321
|
-
}
|
|
6322
|
-
const selectedShas = await promptSelectCommits(sortedCommits);
|
|
6323
|
-
if (selectedShas.length === 0) {
|
|
6324
|
-
logger.log("");
|
|
6325
|
-
logger.info(theme.secondary("No PRs selected. Scan cancelled."));
|
|
6326
|
-
logger.log("");
|
|
6327
|
-
return;
|
|
6328
|
-
}
|
|
6329
|
-
const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
|
|
6330
|
-
logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
|
|
6331
|
-
logger.log("");
|
|
6332
|
-
const existingBrags = await apiService.listBrags({ limit: 100 });
|
|
6333
|
-
logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
|
|
6334
|
-
const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
|
|
6335
|
-
logger.debug(`Existing PR URLs in attachments: ${existingUrls.size}`);
|
|
6336
|
-
const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
|
|
6337
|
-
const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
|
|
6338
|
-
logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
|
|
6339
|
-
if (duplicates.length > 0) {
|
|
6340
|
-
logger.log("");
|
|
6341
|
-
logger.info(
|
|
6342
|
-
colors.warning(
|
|
6343
|
-
`${duplicates.length} PR${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
|
|
6344
|
-
)
|
|
6345
|
-
);
|
|
6346
|
-
logger.log("");
|
|
6347
|
-
}
|
|
6348
|
-
if (newCommits.length === 0) {
|
|
6349
|
-
logger.log("");
|
|
6350
|
-
logger.info(
|
|
6351
|
-
theme.secondary("All selected PRs already exist in Bragduck. Nothing to refine.")
|
|
6352
|
-
);
|
|
6353
|
-
logger.log("");
|
|
6354
|
-
return;
|
|
6355
|
-
}
|
|
6356
|
-
const refineSpinner = createStepSpinner(
|
|
6357
|
-
3,
|
|
6358
|
-
TOTAL_STEPS,
|
|
6359
|
-
`Refining ${theme.count(newCommits.length)} PR${newCommits.length > 1 ? "s" : ""} with AI`
|
|
6360
|
-
);
|
|
6361
|
-
refineSpinner.start();
|
|
6362
|
-
const refineRequest = {
|
|
6363
|
-
brags: newCommits.map((c) => ({
|
|
6364
|
-
text: c.message,
|
|
6365
|
-
date: c.date,
|
|
6366
|
-
title: c.message.split("\n")[0]
|
|
6367
|
-
// First line as initial title
|
|
6368
|
-
}))
|
|
6369
|
-
};
|
|
6370
|
-
const refineResponse = await apiService.refineBrags(refineRequest);
|
|
6371
|
-
let refinedBrags = refineResponse.refined_brags;
|
|
6372
|
-
succeedStepSpinner(refineSpinner, 3, TOTAL_STEPS, "PRs refined successfully");
|
|
6373
|
-
logger.log("");
|
|
6374
|
-
logger.info("Preview of refined brags:");
|
|
6375
|
-
logger.log("");
|
|
6376
|
-
logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
|
|
6377
|
-
logger.log("");
|
|
6378
|
-
const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
|
|
6379
|
-
if (acceptedBrags.length === 0) {
|
|
6380
|
-
logger.log("");
|
|
6381
|
-
logger.info(theme.secondary("No brags selected for creation. Scan cancelled."));
|
|
6382
|
-
logger.log("");
|
|
6383
|
-
return;
|
|
6384
|
-
}
|
|
6385
|
-
logger.log("");
|
|
6386
|
-
let selectedOrgId = null;
|
|
6387
|
-
const userInfo = authService.getUserInfo();
|
|
6388
|
-
if (userInfo?.id) {
|
|
6389
|
-
try {
|
|
6390
|
-
const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
|
|
6391
|
-
if (orgsResponse.items.length > 0) {
|
|
6392
|
-
selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
|
|
6393
|
-
logger.log("");
|
|
6394
|
-
}
|
|
6395
|
-
} catch {
|
|
6396
|
-
logger.debug("Failed to fetch organisations, skipping org selection");
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
const createSpinner2 = createStepSpinner(
|
|
6400
|
-
4,
|
|
6401
|
-
TOTAL_STEPS,
|
|
6402
|
-
`Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
|
|
6403
|
-
);
|
|
6404
|
-
createSpinner2.start();
|
|
6405
|
-
const createRequest = {
|
|
6406
|
-
brags: acceptedBrags.map((refined, index) => {
|
|
6407
|
-
const originalCommit = newCommits[index];
|
|
6408
|
-
const repoTag = repoInfo.name;
|
|
6409
|
-
const tagsWithRepo = refined.suggested_tags.includes(repoTag) ? refined.suggested_tags : [repoTag, ...refined.suggested_tags];
|
|
6410
|
-
return {
|
|
6411
|
-
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
6412
|
-
title: refined.refined_title,
|
|
6413
|
-
description: refined.refined_description,
|
|
6414
|
-
tags: tagsWithRepo,
|
|
6415
|
-
repository: repoInfo.url,
|
|
6416
|
-
date: originalCommit?.date || "",
|
|
6417
|
-
commit_url: originalCommit?.url || "",
|
|
6418
|
-
impact_score: refined.suggested_impactLevel,
|
|
6419
|
-
impact_description: refined.impact_description,
|
|
6420
|
-
attachments: originalCommit?.url ? [originalCommit.url] : [],
|
|
6421
|
-
orgId: selectedOrgId || void 0
|
|
6422
|
-
};
|
|
6423
|
-
})
|
|
6424
|
-
};
|
|
6425
|
-
const createResponse = await apiService.createBrags(createRequest);
|
|
6426
|
-
succeedStepSpinner(
|
|
6427
|
-
createSpinner2,
|
|
6428
|
-
4,
|
|
6429
|
-
TOTAL_STEPS,
|
|
6430
|
-
`Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
|
|
6431
|
-
);
|
|
6432
|
-
logger.log("");
|
|
6433
|
-
logger.log(boxen8(formatSuccessMessage(createResponse.created), boxStyles.success));
|
|
6434
|
-
} catch (error) {
|
|
6435
|
-
if (error instanceof CancelPromptError) {
|
|
6436
|
-
logger.log("");
|
|
6437
|
-
logger.info(theme.secondary("Scan cancelled."));
|
|
6438
|
-
logger.log("");
|
|
6439
|
-
return;
|
|
6440
|
-
}
|
|
6441
|
-
const err = error;
|
|
6442
|
-
logger.log("");
|
|
6443
|
-
logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
|
|
6444
|
-
process.exit(1);
|
|
6445
|
-
}
|
|
6446
|
-
}
|
|
6447
|
-
function getErrorHint3(error) {
|
|
6448
|
-
if (error.name === "GitHubError") {
|
|
6449
|
-
return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
|
|
6450
|
-
}
|
|
6451
|
-
if (error.name === "GitError") {
|
|
6452
|
-
return "Make sure you are in a git repository. Note: Only GitHub repositories are supported for PR scanning.";
|
|
6453
|
-
}
|
|
6454
|
-
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
6455
|
-
return 'Run "bragduck init" to login again';
|
|
6456
|
-
}
|
|
6457
|
-
if (error.name === "NetworkError") {
|
|
6458
|
-
return "Check your internet connection and try again";
|
|
6459
|
-
}
|
|
6460
|
-
if (error.name === "ApiError") {
|
|
6461
|
-
return "The server might be experiencing issues. Try again later";
|
|
6462
|
-
}
|
|
6463
|
-
return "Try running with DEBUG=* for more information";
|
|
6464
|
-
}
|
|
6465
|
-
|
|
6466
6388
|
// src/commands/list.ts
|
|
6467
6389
|
init_esm_shims();
|
|
6468
6390
|
init_api_service();
|
|
6469
|
-
import
|
|
6391
|
+
import boxen7 from "boxen";
|
|
6470
6392
|
import Table2 from "cli-table3";
|
|
6471
6393
|
init_logger();
|
|
6472
6394
|
async function listCommand(options = {}) {
|
|
@@ -6533,7 +6455,7 @@ async function listCommand(options = {}) {
|
|
|
6533
6455
|
} catch (error) {
|
|
6534
6456
|
const err = error;
|
|
6535
6457
|
logger.log("");
|
|
6536
|
-
logger.log(
|
|
6458
|
+
logger.log(boxen7(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
|
|
6537
6459
|
process.exit(1);
|
|
6538
6460
|
}
|
|
6539
6461
|
}
|
|
@@ -6555,7 +6477,7 @@ function formatBragsTable(brags) {
|
|
|
6555
6477
|
const title = brag.title;
|
|
6556
6478
|
const description = truncateText(brag.description, 100);
|
|
6557
6479
|
const tags = brag.tags.length > 0 ? brag.tags.join(", ") : colors.dim("none");
|
|
6558
|
-
const repository = brag.
|
|
6480
|
+
const repository = brag.externalUrl ? truncateText(extractRepoName(brag.externalUrl), 25) : colors.dim("none");
|
|
6559
6481
|
table.push([
|
|
6560
6482
|
colors.highlight(date),
|
|
6561
6483
|
colors.white(title),
|
|
@@ -6586,7 +6508,7 @@ function extractRepoName(url) {
|
|
|
6586
6508
|
return url;
|
|
6587
6509
|
}
|
|
6588
6510
|
}
|
|
6589
|
-
function
|
|
6511
|
+
function getErrorHint2(error) {
|
|
6590
6512
|
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
6591
6513
|
return 'Run "bragduck init" to login again';
|
|
6592
6514
|
}
|
|
@@ -6604,8 +6526,8 @@ init_esm_shims();
|
|
|
6604
6526
|
init_storage_service();
|
|
6605
6527
|
init_logger();
|
|
6606
6528
|
init_constants();
|
|
6607
|
-
import
|
|
6608
|
-
import
|
|
6529
|
+
import boxen8 from "boxen";
|
|
6530
|
+
import chalk6 from "chalk";
|
|
6609
6531
|
import Table3 from "cli-table3";
|
|
6610
6532
|
init_errors();
|
|
6611
6533
|
var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
|
|
@@ -6640,7 +6562,7 @@ async function configCommand(subcommand, key, value) {
|
|
|
6640
6562
|
const err = error;
|
|
6641
6563
|
logger.log("");
|
|
6642
6564
|
logger.log(
|
|
6643
|
-
|
|
6565
|
+
boxen8(formatErrorMessage(err.message, getConfigHint(err)), {
|
|
6644
6566
|
padding: 1,
|
|
6645
6567
|
margin: 1,
|
|
6646
6568
|
borderStyle: "round",
|
|
@@ -6661,7 +6583,7 @@ async function handleListConfig() {
|
|
|
6661
6583
|
gitlabInstance: storageService.getConfig("gitlabInstance")
|
|
6662
6584
|
};
|
|
6663
6585
|
const table = new Table3({
|
|
6664
|
-
head: [
|
|
6586
|
+
head: [chalk6.cyan("Key"), chalk6.cyan("Value"), chalk6.cyan("Default")],
|
|
6665
6587
|
colWidths: [25, 40, 40],
|
|
6666
6588
|
wordWrap: true,
|
|
6667
6589
|
style: {
|
|
@@ -6670,61 +6592,61 @@ async function handleListConfig() {
|
|
|
6670
6592
|
}
|
|
6671
6593
|
});
|
|
6672
6594
|
table.push([
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6595
|
+
chalk6.white("defaultCommitDays"),
|
|
6596
|
+
chalk6.yellow(String(config2.defaultCommitDays)),
|
|
6597
|
+
chalk6.dim(String(DEFAULT_CONFIG.defaultCommitDays))
|
|
6676
6598
|
]);
|
|
6677
6599
|
table.push([
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6600
|
+
chalk6.white("autoVersionCheck"),
|
|
6601
|
+
chalk6.yellow(String(config2.autoVersionCheck)),
|
|
6602
|
+
chalk6.dim(String(DEFAULT_CONFIG.autoVersionCheck))
|
|
6681
6603
|
]);
|
|
6682
6604
|
table.push([
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6605
|
+
chalk6.white("defaultSource"),
|
|
6606
|
+
chalk6.yellow(config2.defaultSource || "not set"),
|
|
6607
|
+
chalk6.dim("not set")
|
|
6686
6608
|
]);
|
|
6687
6609
|
table.push([
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6610
|
+
chalk6.white("sourcePriority"),
|
|
6611
|
+
chalk6.yellow(config2.sourcePriority ? config2.sourcePriority.join(", ") : "not set"),
|
|
6612
|
+
chalk6.dim("not set")
|
|
6691
6613
|
]);
|
|
6692
6614
|
table.push([
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6615
|
+
chalk6.white("jiraInstance"),
|
|
6616
|
+
chalk6.yellow(config2.jiraInstance || "not set"),
|
|
6617
|
+
chalk6.dim("not set")
|
|
6696
6618
|
]);
|
|
6697
6619
|
table.push([
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6620
|
+
chalk6.white("confluenceInstance"),
|
|
6621
|
+
chalk6.yellow(config2.confluenceInstance || "not set"),
|
|
6622
|
+
chalk6.dim("not set")
|
|
6701
6623
|
]);
|
|
6702
6624
|
table.push([
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6625
|
+
chalk6.white("gitlabInstance"),
|
|
6626
|
+
chalk6.yellow(config2.gitlabInstance || "not set"),
|
|
6627
|
+
chalk6.dim("not set")
|
|
6706
6628
|
]);
|
|
6707
6629
|
logger.info("Current configuration:");
|
|
6708
6630
|
logger.log("");
|
|
6709
6631
|
logger.log(table.toString());
|
|
6710
6632
|
logger.log("");
|
|
6711
|
-
logger.info(
|
|
6633
|
+
logger.info(chalk6.dim("To change a value: ") + chalk6.cyan("bragduck config set <key> <value>"));
|
|
6712
6634
|
logger.log("");
|
|
6713
6635
|
}
|
|
6714
6636
|
async function handleGetConfig(key) {
|
|
6715
6637
|
validateConfigKey(key);
|
|
6716
6638
|
const value = storageService.getConfig(key);
|
|
6717
6639
|
const defaultValue = DEFAULT_CONFIG[key];
|
|
6718
|
-
logger.info(`Configuration for ${
|
|
6640
|
+
logger.info(`Configuration for ${chalk6.cyan(key)}:`);
|
|
6719
6641
|
logger.log("");
|
|
6720
|
-
logger.log(` ${
|
|
6721
|
-
logger.log(` ${
|
|
6642
|
+
logger.log(` ${chalk6.white("Current:")} ${chalk6.yellow(String(value))}`);
|
|
6643
|
+
logger.log(` ${chalk6.white("Default:")} ${chalk6.dim(String(defaultValue))}`);
|
|
6722
6644
|
logger.log("");
|
|
6723
6645
|
if (value === defaultValue) {
|
|
6724
|
-
logger.info(
|
|
6646
|
+
logger.info(chalk6.dim("Using default value"));
|
|
6725
6647
|
} else {
|
|
6726
6648
|
logger.info(
|
|
6727
|
-
|
|
6649
|
+
chalk6.dim("Custom value set. Reset with: ") + chalk6.cyan(`bragduck config set ${key} ${defaultValue}`)
|
|
6728
6650
|
);
|
|
6729
6651
|
}
|
|
6730
6652
|
logger.log("");
|
|
@@ -6734,10 +6656,10 @@ async function handleSetConfig(key, value) {
|
|
|
6734
6656
|
const typedValue = validateAndConvertValue(key, value);
|
|
6735
6657
|
storageService.setConfig(key, typedValue);
|
|
6736
6658
|
logger.log(
|
|
6737
|
-
|
|
6738
|
-
`${
|
|
6659
|
+
boxen8(
|
|
6660
|
+
`${chalk6.green.bold("\u2713 Configuration updated")}
|
|
6739
6661
|
|
|
6740
|
-
${
|
|
6662
|
+
${chalk6.white(key)}: ${chalk6.yellow(String(typedValue))}`,
|
|
6741
6663
|
{
|
|
6742
6664
|
padding: 1,
|
|
6743
6665
|
margin: 1,
|
|
@@ -6834,12 +6756,12 @@ function getConfigHint(error) {
|
|
|
6834
6756
|
// src/cli.ts
|
|
6835
6757
|
init_version();
|
|
6836
6758
|
init_logger();
|
|
6837
|
-
var
|
|
6838
|
-
var
|
|
6839
|
-
var packageJsonPath =
|
|
6840
|
-
var packageJson = JSON.parse(
|
|
6759
|
+
var __filename7 = fileURLToPath7(import.meta.url);
|
|
6760
|
+
var __dirname7 = dirname6(__filename7);
|
|
6761
|
+
var packageJsonPath = join8(__dirname7, "../../package.json");
|
|
6762
|
+
var packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
6841
6763
|
var program = new Command();
|
|
6842
|
-
program.name("bragduck").description("CLI tool for managing developer achievements and brags\nAliases:
|
|
6764
|
+
program.name("bragduck").description("CLI tool for managing developer achievements and brags\nAliases: duck, brag").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)");
|
|
6843
6765
|
program.command("auth [subcommand]").description("Manage authentication (subcommands: login, status, bitbucket, gitlab)").action(async (subcommand) => {
|
|
6844
6766
|
try {
|
|
6845
6767
|
await authCommand(subcommand);
|
|
@@ -6856,22 +6778,6 @@ program.command("sync").description("Sync work items and create brags").option("
|
|
|
6856
6778
|
process.exit(1);
|
|
6857
6779
|
}
|
|
6858
6780
|
});
|
|
6859
|
-
program.command("init").description("Authenticate with Bragduck").action(async () => {
|
|
6860
|
-
try {
|
|
6861
|
-
await initCommand();
|
|
6862
|
-
} catch (error) {
|
|
6863
|
-
console.error(error);
|
|
6864
|
-
process.exit(1);
|
|
6865
|
-
}
|
|
6866
|
-
});
|
|
6867
|
-
program.command("scan").description("Scan git commits and create brags (deprecated, use sync)").option("-d, --days <number>", "Number of days to scan", (val) => parseInt(val, 10)).option("-a, --all", "Include all commits (not just current user)").action(async (options) => {
|
|
6868
|
-
try {
|
|
6869
|
-
await scanCommand(options);
|
|
6870
|
-
} catch (error) {
|
|
6871
|
-
console.error(error);
|
|
6872
|
-
process.exit(1);
|
|
6873
|
-
}
|
|
6874
|
-
});
|
|
6875
6781
|
program.command("list").description("List your existing brags").option("-l, --limit <number>", "Number of brags to display", (val) => parseInt(val, 10), 50).option("-o, --offset <number>", "Number of brags to skip", (val) => parseInt(val, 10), 0).option("-t, --tags <tags>", "Filter by tags (comma-separated)").option("-s, --search <query>", "Search brags by keyword").option("--oneline", "Show only date and title for each brag").action(async (options) => {
|
|
6876
6782
|
try {
|
|
6877
6783
|
await listCommand(options);
|