@bragduck/cli 2.3.11 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bragduck.js +1470 -397
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -232,6 +232,82 @@ var init_storage_service = __esm({
|
|
|
232
232
|
}
|
|
233
233
|
return true;
|
|
234
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Get credentials for a specific service
|
|
237
|
+
*/
|
|
238
|
+
async getServiceCredentials(service) {
|
|
239
|
+
const credentials = await this.getCredentials();
|
|
240
|
+
if (!credentials) return null;
|
|
241
|
+
if (service === "bragduck" && credentials.accessToken && !credentials.services) {
|
|
242
|
+
return {
|
|
243
|
+
accessToken: credentials.accessToken,
|
|
244
|
+
refreshToken: credentials.refreshToken,
|
|
245
|
+
expiresAt: credentials.expiresAt
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return credentials.services?.[service] || null;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Set credentials for a specific service
|
|
252
|
+
*/
|
|
253
|
+
async setServiceCredentials(service, creds) {
|
|
254
|
+
let existing = await this.getCredentials();
|
|
255
|
+
if (existing && existing.accessToken && !existing.services) {
|
|
256
|
+
existing = {
|
|
257
|
+
services: {
|
|
258
|
+
bragduck: {
|
|
259
|
+
accessToken: existing.accessToken,
|
|
260
|
+
refreshToken: existing.refreshToken,
|
|
261
|
+
expiresAt: existing.expiresAt
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const updated = {
|
|
267
|
+
...existing,
|
|
268
|
+
services: {
|
|
269
|
+
...existing?.services,
|
|
270
|
+
[service]: creds
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
await this.setCredentials(updated);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Delete credentials for a specific service
|
|
277
|
+
*/
|
|
278
|
+
async deleteServiceCredentials(service) {
|
|
279
|
+
const existing = await this.getCredentials();
|
|
280
|
+
if (!existing?.services?.[service]) return;
|
|
281
|
+
delete existing.services[service];
|
|
282
|
+
await this.setCredentials(existing);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Check if a specific service is authenticated
|
|
286
|
+
*/
|
|
287
|
+
async isServiceAuthenticated(service) {
|
|
288
|
+
const creds = await this.getServiceCredentials(service);
|
|
289
|
+
if (!creds?.accessToken) return false;
|
|
290
|
+
if (creds.expiresAt && creds.expiresAt < Date.now()) return false;
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* List all authenticated services
|
|
295
|
+
*/
|
|
296
|
+
async getAuthenticatedServices() {
|
|
297
|
+
const credentials = await this.getCredentials();
|
|
298
|
+
if (!credentials) return [];
|
|
299
|
+
if (credentials.accessToken && !credentials.services) {
|
|
300
|
+
return ["bragduck"];
|
|
301
|
+
}
|
|
302
|
+
if (!credentials.services) return [];
|
|
303
|
+
const services = [];
|
|
304
|
+
for (const [service, creds] of Object.entries(credentials.services)) {
|
|
305
|
+
if (creds?.accessToken) {
|
|
306
|
+
services.push(service);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return services;
|
|
310
|
+
}
|
|
235
311
|
/**
|
|
236
312
|
* Store user information
|
|
237
313
|
*/
|
|
@@ -316,7 +392,7 @@ var init_storage_service = __esm({
|
|
|
316
392
|
});
|
|
317
393
|
|
|
318
394
|
// src/utils/errors.ts
|
|
319
|
-
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError;
|
|
395
|
+
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError;
|
|
320
396
|
var init_errors = __esm({
|
|
321
397
|
"src/utils/errors.ts"() {
|
|
322
398
|
"use strict";
|
|
@@ -383,6 +459,12 @@ var init_errors = __esm({
|
|
|
383
459
|
this.name = "GitHubError";
|
|
384
460
|
}
|
|
385
461
|
};
|
|
462
|
+
BitbucketError = class extends BragduckError {
|
|
463
|
+
constructor(message, details) {
|
|
464
|
+
super(message, "BITBUCKET_ERROR", details);
|
|
465
|
+
this.name = "BitbucketError";
|
|
466
|
+
}
|
|
467
|
+
};
|
|
386
468
|
}
|
|
387
469
|
});
|
|
388
470
|
|
|
@@ -932,12 +1014,12 @@ __export(version_exports, {
|
|
|
932
1014
|
});
|
|
933
1015
|
import { readFileSync as readFileSync3 } from "fs";
|
|
934
1016
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
935
|
-
import { dirname as dirname3, join as
|
|
1017
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
936
1018
|
import chalk4 from "chalk";
|
|
937
|
-
import
|
|
1019
|
+
import boxen2 from "boxen";
|
|
938
1020
|
function getCurrentVersion() {
|
|
939
1021
|
try {
|
|
940
|
-
const packageJsonPath2 =
|
|
1022
|
+
const packageJsonPath2 = join4(__dirname4, "../../package.json");
|
|
941
1023
|
const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
|
|
942
1024
|
return packageJson2.version;
|
|
943
1025
|
} catch {
|
|
@@ -1003,7 +1085,7 @@ Latest version: ${chalk4.green(latestVersion)}
|
|
|
1003
1085
|
Update with: ${chalk4.cyan("npm install -g @bragduck/cli@latest")}`;
|
|
1004
1086
|
console.log("");
|
|
1005
1087
|
console.log(
|
|
1006
|
-
|
|
1088
|
+
boxen2(message, {
|
|
1007
1089
|
padding: 1,
|
|
1008
1090
|
margin: { top: 0, right: 1, bottom: 0, left: 1 },
|
|
1009
1091
|
borderStyle: "round",
|
|
@@ -1039,10 +1121,10 @@ import { ofetch as ofetch2 } from "ofetch";
|
|
|
1039
1121
|
import { readFileSync as readFileSync4 } from "fs";
|
|
1040
1122
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1041
1123
|
import { URLSearchParams as URLSearchParams2 } from "url";
|
|
1042
|
-
import { dirname as dirname4, join as
|
|
1124
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
1043
1125
|
function getCliVersion() {
|
|
1044
1126
|
try {
|
|
1045
|
-
const packageJsonPath2 =
|
|
1127
|
+
const packageJsonPath2 = join5(__dirname5, "../../package.json");
|
|
1046
1128
|
const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
|
|
1047
1129
|
return packageJson2.version;
|
|
1048
1130
|
} catch {
|
|
@@ -1354,29 +1436,205 @@ import { readFileSync as readFileSync5 } from "fs";
|
|
|
1354
1436
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
1355
1437
|
import { dirname as dirname5, join as join7 } from "path";
|
|
1356
1438
|
|
|
1357
|
-
// src/commands/
|
|
1439
|
+
// src/commands/auth.ts
|
|
1358
1440
|
init_esm_shims();
|
|
1359
1441
|
init_auth_service();
|
|
1442
|
+
init_storage_service();
|
|
1360
1443
|
init_logger();
|
|
1361
|
-
import ora from "ora";
|
|
1362
1444
|
import boxen from "boxen";
|
|
1445
|
+
import chalk3 from "chalk";
|
|
1446
|
+
import { input } from "@inquirer/prompts";
|
|
1447
|
+
|
|
1448
|
+
// src/ui/theme.ts
|
|
1449
|
+
init_esm_shims();
|
|
1363
1450
|
import chalk2 from "chalk";
|
|
1364
|
-
|
|
1451
|
+
var colors = {
|
|
1452
|
+
// Primary colors for main actions and interactive elements
|
|
1453
|
+
primary: chalk2.cyan,
|
|
1454
|
+
// Success states
|
|
1455
|
+
success: chalk2.green,
|
|
1456
|
+
successBold: chalk2.green.bold,
|
|
1457
|
+
// Warning states
|
|
1458
|
+
warning: chalk2.yellow,
|
|
1459
|
+
warningBold: chalk2.yellow.bold,
|
|
1460
|
+
// Error states
|
|
1461
|
+
error: chalk2.red,
|
|
1462
|
+
errorBold: chalk2.red.bold,
|
|
1463
|
+
// Info and metadata
|
|
1464
|
+
info: chalk2.gray,
|
|
1465
|
+
infoDim: chalk2.dim,
|
|
1466
|
+
// Highlighted/important data
|
|
1467
|
+
highlight: chalk2.yellow.bold,
|
|
1468
|
+
highlightCyan: chalk2.cyan.bold,
|
|
1469
|
+
// Text emphasis
|
|
1470
|
+
bold: chalk2.bold,
|
|
1471
|
+
dim: chalk2.dim,
|
|
1472
|
+
// Special purpose
|
|
1473
|
+
white: chalk2.white,
|
|
1474
|
+
gray: chalk2.gray,
|
|
1475
|
+
// Links and URLs
|
|
1476
|
+
link: chalk2.blue.underline
|
|
1477
|
+
};
|
|
1478
|
+
var theme = {
|
|
1479
|
+
/**
|
|
1480
|
+
* Format command names and CLI actions
|
|
1481
|
+
*/
|
|
1482
|
+
command: (text) => colors.primary(text),
|
|
1483
|
+
/**
|
|
1484
|
+
* Format success messages
|
|
1485
|
+
*/
|
|
1486
|
+
success: (text) => colors.success(`\u2713 ${text}`),
|
|
1487
|
+
successBold: (text) => colors.successBold(`\u2713 ${text}`),
|
|
1488
|
+
/**
|
|
1489
|
+
* Format error messages
|
|
1490
|
+
*/
|
|
1491
|
+
error: (text) => colors.error(`\u2717 ${text}`),
|
|
1492
|
+
errorBold: (text) => colors.errorBold(`\u2717 ${text}`),
|
|
1493
|
+
/**
|
|
1494
|
+
* Format warning messages
|
|
1495
|
+
*/
|
|
1496
|
+
warning: (text) => colors.warning(`\u26A0 ${text}`),
|
|
1497
|
+
/**
|
|
1498
|
+
* Format info messages
|
|
1499
|
+
*/
|
|
1500
|
+
info: (text) => colors.info(text),
|
|
1501
|
+
/**
|
|
1502
|
+
* Format labels (e.g., "User:", "Email:")
|
|
1503
|
+
*/
|
|
1504
|
+
label: (text) => colors.gray(`${text}:`),
|
|
1505
|
+
/**
|
|
1506
|
+
* Format values
|
|
1507
|
+
*/
|
|
1508
|
+
value: (text) => colors.highlight(text),
|
|
1509
|
+
/**
|
|
1510
|
+
* Format counts and numbers
|
|
1511
|
+
*/
|
|
1512
|
+
count: (num) => colors.highlightCyan(num.toString()),
|
|
1513
|
+
/**
|
|
1514
|
+
* Format file paths
|
|
1515
|
+
*/
|
|
1516
|
+
path: (text) => colors.info(text),
|
|
1517
|
+
/**
|
|
1518
|
+
* Format step indicators (e.g., "Step 1/5:")
|
|
1519
|
+
*/
|
|
1520
|
+
step: (current, total) => colors.primary(`[${current}/${total}]`),
|
|
1521
|
+
/**
|
|
1522
|
+
* Format progress text
|
|
1523
|
+
*/
|
|
1524
|
+
progress: (text) => colors.infoDim(text),
|
|
1525
|
+
/**
|
|
1526
|
+
* Format emphasized text
|
|
1527
|
+
*/
|
|
1528
|
+
emphasis: (text) => colors.bold(text),
|
|
1529
|
+
/**
|
|
1530
|
+
* Format de-emphasized/secondary text
|
|
1531
|
+
*/
|
|
1532
|
+
secondary: (text) => colors.dim(text),
|
|
1533
|
+
/**
|
|
1534
|
+
* Format interactive elements (prompts, selections)
|
|
1535
|
+
*/
|
|
1536
|
+
interactive: (text) => colors.primary(text),
|
|
1537
|
+
/**
|
|
1538
|
+
* Format keys in key-value pairs
|
|
1539
|
+
*/
|
|
1540
|
+
key: (text) => colors.white(text)
|
|
1541
|
+
};
|
|
1542
|
+
var boxStyles = {
|
|
1543
|
+
success: {
|
|
1544
|
+
borderColor: "green",
|
|
1545
|
+
borderStyle: "round",
|
|
1546
|
+
padding: 1,
|
|
1547
|
+
margin: 1
|
|
1548
|
+
},
|
|
1549
|
+
error: {
|
|
1550
|
+
borderColor: "red",
|
|
1551
|
+
borderStyle: "round",
|
|
1552
|
+
padding: 1,
|
|
1553
|
+
margin: 1
|
|
1554
|
+
},
|
|
1555
|
+
warning: {
|
|
1556
|
+
borderColor: "yellow",
|
|
1557
|
+
borderStyle: "round",
|
|
1558
|
+
padding: 1,
|
|
1559
|
+
margin: 1
|
|
1560
|
+
},
|
|
1561
|
+
info: {
|
|
1562
|
+
borderColor: "cyan",
|
|
1563
|
+
borderStyle: "round",
|
|
1564
|
+
padding: 1,
|
|
1565
|
+
margin: 1
|
|
1566
|
+
},
|
|
1567
|
+
plain: {
|
|
1568
|
+
borderColor: "gray",
|
|
1569
|
+
borderStyle: "round",
|
|
1570
|
+
padding: 1,
|
|
1571
|
+
margin: 1
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
var tableStyles = {
|
|
1575
|
+
default: {
|
|
1576
|
+
style: {
|
|
1577
|
+
head: [],
|
|
1578
|
+
border: ["gray"]
|
|
1579
|
+
}
|
|
1580
|
+
},
|
|
1581
|
+
compact: {
|
|
1582
|
+
style: {
|
|
1583
|
+
head: [],
|
|
1584
|
+
border: ["dim"],
|
|
1585
|
+
compact: true
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
var sizeIndicators = {
|
|
1590
|
+
small: colors.success("\u25CF"),
|
|
1591
|
+
medium: colors.warning("\u25CF"),
|
|
1592
|
+
large: colors.error("\u25CF")
|
|
1593
|
+
};
|
|
1594
|
+
function getSizeIndicator(insertions, deletions) {
|
|
1595
|
+
const total = insertions + deletions;
|
|
1596
|
+
if (total < 50) {
|
|
1597
|
+
return `${sizeIndicators.small} Small`;
|
|
1598
|
+
} else if (total < 200) {
|
|
1599
|
+
return `${sizeIndicators.medium} Medium`;
|
|
1600
|
+
} else {
|
|
1601
|
+
return `${sizeIndicators.large} Large`;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
function formatDiffStats(insertions, deletions) {
|
|
1605
|
+
return `${colors.success(`+${insertions}`)} ${colors.error(`-${deletions}`)}`;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// src/commands/auth.ts
|
|
1609
|
+
async function authCommand(subcommand) {
|
|
1610
|
+
if (!subcommand || subcommand === "login") {
|
|
1611
|
+
await authLogin();
|
|
1612
|
+
} else if (subcommand === "status") {
|
|
1613
|
+
await authStatus();
|
|
1614
|
+
} else if (subcommand === "bitbucket") {
|
|
1615
|
+
await authBitbucket();
|
|
1616
|
+
} else {
|
|
1617
|
+
logger.error(`Unknown auth subcommand: ${subcommand}`);
|
|
1618
|
+
logger.info("Available subcommands: login, status, bitbucket");
|
|
1619
|
+
process.exit(1);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async function authLogin() {
|
|
1365
1623
|
logger.log("");
|
|
1366
|
-
logger.info("
|
|
1624
|
+
logger.info("Authenticating with Bragduck...");
|
|
1367
1625
|
logger.log("");
|
|
1368
|
-
const isAuthenticated = await
|
|
1626
|
+
const isAuthenticated = await storageService.isServiceAuthenticated("bragduck");
|
|
1369
1627
|
if (isAuthenticated) {
|
|
1370
1628
|
const userInfo = authService.getUserInfo();
|
|
1371
1629
|
if (userInfo) {
|
|
1372
1630
|
logger.log(
|
|
1373
1631
|
boxen(
|
|
1374
|
-
`${
|
|
1632
|
+
`${chalk3.yellow("Already authenticated!")}
|
|
1375
1633
|
|
|
1376
|
-
${
|
|
1377
|
-
${
|
|
1634
|
+
${chalk3.gray("User:")} ${userInfo.name}
|
|
1635
|
+
${chalk3.gray("Email:")} ${userInfo.email}
|
|
1378
1636
|
|
|
1379
|
-
${
|
|
1637
|
+
${chalk3.dim("Run")} ${chalk3.cyan("bragduck logout")} ${chalk3.dim("to sign out")}`,
|
|
1380
1638
|
{
|
|
1381
1639
|
padding: 1,
|
|
1382
1640
|
margin: 1,
|
|
@@ -1386,26 +1644,20 @@ ${chalk2.dim("Run")} ${chalk2.cyan("bragduck logout")} ${chalk2.dim("to sign out
|
|
|
1386
1644
|
)
|
|
1387
1645
|
);
|
|
1388
1646
|
logger.log("");
|
|
1389
|
-
globalThis.setTimeout(() => {
|
|
1390
|
-
process.exit(0);
|
|
1391
|
-
}, 100);
|
|
1392
1647
|
return;
|
|
1393
1648
|
}
|
|
1394
1649
|
}
|
|
1395
|
-
const spinner = ora("Opening browser for authentication...").start();
|
|
1396
1650
|
try {
|
|
1397
|
-
spinner.text = "Waiting for authentication...";
|
|
1398
1651
|
const userInfo = await authService.login();
|
|
1399
|
-
spinner.succeed("Authentication successful!");
|
|
1400
1652
|
logger.log("");
|
|
1401
1653
|
logger.log(
|
|
1402
1654
|
boxen(
|
|
1403
|
-
`${
|
|
1655
|
+
`${chalk3.green.bold("\u2713 Successfully authenticated!")}
|
|
1404
1656
|
|
|
1405
|
-
${
|
|
1406
|
-
${
|
|
1657
|
+
${chalk3.gray("Welcome,")} ${chalk3.cyan(userInfo.name)}
|
|
1658
|
+
${chalk3.gray("Email:")} ${userInfo.email}
|
|
1407
1659
|
|
|
1408
|
-
${
|
|
1660
|
+
${chalk3.dim("Use")} ${chalk3.cyan("bragduck sync")} ${chalk3.dim("to create brags")}`,
|
|
1409
1661
|
{
|
|
1410
1662
|
padding: 1,
|
|
1411
1663
|
margin: 1,
|
|
@@ -1415,118 +1667,129 @@ ${chalk2.dim("You can now use")} ${chalk2.cyan("bragduck scan")} ${chalk2.dim("t
|
|
|
1415
1667
|
)
|
|
1416
1668
|
);
|
|
1417
1669
|
logger.log("");
|
|
1418
|
-
globalThis.setTimeout(() => {
|
|
1419
|
-
process.exit(0);
|
|
1420
|
-
}, 100);
|
|
1421
|
-
return;
|
|
1422
1670
|
} catch (error) {
|
|
1423
|
-
spinner.fail("Authentication failed");
|
|
1424
|
-
logger.log("");
|
|
1425
1671
|
const err = error;
|
|
1672
|
+
logger.log("");
|
|
1426
1673
|
logger.log(
|
|
1427
|
-
boxen(
|
|
1428
|
-
`${chalk2.red.bold("\u2717 Authentication Failed")}
|
|
1674
|
+
boxen(`${chalk3.red.bold("\u2717 Authentication Failed")}
|
|
1429
1675
|
|
|
1430
|
-
${err.message}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
borderStyle: "round",
|
|
1437
|
-
borderColor: "red"
|
|
1438
|
-
}
|
|
1439
|
-
)
|
|
1676
|
+
${err.message}`, {
|
|
1677
|
+
padding: 1,
|
|
1678
|
+
margin: 1,
|
|
1679
|
+
borderStyle: "round",
|
|
1680
|
+
borderColor: "red"
|
|
1681
|
+
})
|
|
1440
1682
|
);
|
|
1441
1683
|
process.exit(1);
|
|
1442
1684
|
}
|
|
1443
1685
|
}
|
|
1444
|
-
function
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1686
|
+
async function authStatus() {
|
|
1687
|
+
logger.log("");
|
|
1688
|
+
logger.info("Authentication Status:");
|
|
1689
|
+
logger.log("");
|
|
1690
|
+
const services = await storageService.getAuthenticatedServices();
|
|
1691
|
+
if (services.length === 0) {
|
|
1692
|
+
logger.info(theme.secondary("Not authenticated with any services"));
|
|
1693
|
+
logger.log("");
|
|
1694
|
+
logger.info(`Run ${theme.command("bragduck auth login")} to authenticate`);
|
|
1695
|
+
logger.log("");
|
|
1696
|
+
return;
|
|
1453
1697
|
}
|
|
1454
|
-
|
|
1455
|
-
|
|
1698
|
+
const bragduckAuth = await storageService.isServiceAuthenticated("bragduck");
|
|
1699
|
+
if (bragduckAuth) {
|
|
1700
|
+
const userInfo = authService.getUserInfo();
|
|
1701
|
+
logger.info(`${theme.success("\u2713")} Bragduck: ${userInfo?.name || "Authenticated"}`);
|
|
1702
|
+
} else {
|
|
1703
|
+
logger.info(`${theme.error("\u2717")} Bragduck: Not authenticated`);
|
|
1456
1704
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1705
|
+
for (const service of services) {
|
|
1706
|
+
if (service !== "bragduck") {
|
|
1707
|
+
logger.info(`${theme.success("\u2713")} ${service}: Authenticated`);
|
|
1708
|
+
}
|
|
1459
1709
|
}
|
|
1460
|
-
|
|
1710
|
+
logger.log("");
|
|
1461
1711
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
import ora2 from "ora";
|
|
1471
|
-
async function logoutCommand() {
|
|
1472
|
-
const isAuthenticated = await authService.isAuthenticated();
|
|
1473
|
-
if (!isAuthenticated) {
|
|
1474
|
-
logger.log(
|
|
1475
|
-
boxen2(
|
|
1476
|
-
`${chalk3.yellow("Not currently authenticated")}
|
|
1477
|
-
|
|
1478
|
-
${chalk3.dim("Nothing to logout from")}`,
|
|
1479
|
-
{
|
|
1480
|
-
padding: 1,
|
|
1481
|
-
margin: 1,
|
|
1482
|
-
borderStyle: "round",
|
|
1483
|
-
borderColor: "yellow"
|
|
1484
|
-
}
|
|
1485
|
-
)
|
|
1486
|
-
);
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
const userInfo = authService.getUserInfo();
|
|
1490
|
-
const userName = userInfo?.name || "Unknown User";
|
|
1712
|
+
async function authBitbucket() {
|
|
1713
|
+
logger.log("");
|
|
1714
|
+
logger.log(
|
|
1715
|
+
boxen(
|
|
1716
|
+
theme.info("Bitbucket API Token Authentication") + "\n\nCreate an API Token at:\n" + colors.highlight("https://bitbucket.org/account/settings/api-token/new") + "\n\nRequired scopes:\n \u2022 pullrequest:read\n \u2022 repository:read\n \u2022 account:read\n\n" + theme.warning("Note: API tokens expire (max 1 year)"),
|
|
1717
|
+
boxStyles.info
|
|
1718
|
+
)
|
|
1719
|
+
);
|
|
1491
1720
|
logger.log("");
|
|
1492
|
-
const shouldLogout = await confirm({
|
|
1493
|
-
message: `Are you sure you want to logout? (${chalk3.cyan(userName)})`,
|
|
1494
|
-
default: false
|
|
1495
|
-
});
|
|
1496
|
-
if (!shouldLogout) {
|
|
1497
|
-
logger.info("Logout cancelled");
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
const spinner = ora2("Logging out...").start();
|
|
1501
1721
|
try {
|
|
1502
|
-
await
|
|
1503
|
-
|
|
1722
|
+
const email = await input({
|
|
1723
|
+
message: "Atlassian account email:",
|
|
1724
|
+
validate: (value) => value.includes("@") ? true : "Please enter a valid email address"
|
|
1725
|
+
});
|
|
1726
|
+
const apiToken = await input({
|
|
1727
|
+
message: "API Token:",
|
|
1728
|
+
validate: (value) => value.length > 0 ? true : "API token cannot be empty"
|
|
1729
|
+
});
|
|
1730
|
+
const tokenExpiry = await input({
|
|
1731
|
+
message: "Token expiry date (YYYY-MM-DD, optional):",
|
|
1732
|
+
default: "",
|
|
1733
|
+
validate: (value) => {
|
|
1734
|
+
if (!value) return true;
|
|
1735
|
+
const date = new Date(value);
|
|
1736
|
+
return !isNaN(date.getTime()) ? true : "Please enter a valid date (YYYY-MM-DD)";
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
1740
|
+
const response = await fetch("https://api.bitbucket.org/2.0/user", {
|
|
1741
|
+
headers: { Authorization: `Basic ${auth}` }
|
|
1742
|
+
});
|
|
1743
|
+
if (!response.ok) {
|
|
1744
|
+
logger.log("");
|
|
1745
|
+
logger.log(
|
|
1746
|
+
boxen(
|
|
1747
|
+
theme.error("\u2717 Authentication Failed") + "\n\nInvalid email or API token",
|
|
1748
|
+
boxStyles.error
|
|
1749
|
+
)
|
|
1750
|
+
);
|
|
1751
|
+
logger.log("");
|
|
1752
|
+
process.exit(1);
|
|
1753
|
+
}
|
|
1754
|
+
const user = await response.json();
|
|
1755
|
+
const credentials = {
|
|
1756
|
+
accessToken: apiToken,
|
|
1757
|
+
username: email
|
|
1758
|
+
// Store email in username field
|
|
1759
|
+
};
|
|
1760
|
+
if (tokenExpiry) {
|
|
1761
|
+
const expiryDate = new Date(tokenExpiry);
|
|
1762
|
+
credentials.expiresAt = expiryDate.getTime();
|
|
1763
|
+
}
|
|
1764
|
+
await storageService.setServiceCredentials("bitbucket", credentials);
|
|
1504
1765
|
logger.log("");
|
|
1505
1766
|
logger.log(
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
${chalk3.dim("Your credentials have been cleared")}
|
|
1767
|
+
boxen(
|
|
1768
|
+
theme.success("\u2713 Successfully authenticated with Bitbucket") + `
|
|
1510
1769
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
borderStyle: "round",
|
|
1516
|
-
borderColor: "green"
|
|
1517
|
-
}
|
|
1770
|
+
Email: ${email}
|
|
1771
|
+
User: ${user.display_name}
|
|
1772
|
+
` + (tokenExpiry ? `Expires: ${tokenExpiry}` : ""),
|
|
1773
|
+
boxStyles.success
|
|
1518
1774
|
)
|
|
1519
1775
|
);
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1776
|
+
logger.log("");
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
const err = error;
|
|
1779
|
+
logger.log("");
|
|
1780
|
+
logger.log(
|
|
1781
|
+
boxen(
|
|
1782
|
+
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
1783
|
+
boxStyles.error
|
|
1784
|
+
)
|
|
1785
|
+
);
|
|
1786
|
+
logger.log("");
|
|
1523
1787
|
process.exit(1);
|
|
1524
1788
|
}
|
|
1525
1789
|
}
|
|
1526
1790
|
|
|
1527
|
-
// src/commands/
|
|
1791
|
+
// src/commands/sync.ts
|
|
1528
1792
|
init_esm_shims();
|
|
1529
|
-
import boxen6 from "boxen";
|
|
1530
1793
|
|
|
1531
1794
|
// node_modules/@inquirer/core/dist/esm/lib/errors.mjs
|
|
1532
1795
|
init_esm_shims();
|
|
@@ -1535,12 +1798,150 @@ var CancelPromptError = class extends Error {
|
|
|
1535
1798
|
message = "Prompt was canceled";
|
|
1536
1799
|
};
|
|
1537
1800
|
|
|
1538
|
-
// src/
|
|
1801
|
+
// src/commands/sync.ts
|
|
1802
|
+
init_api_service();
|
|
1803
|
+
init_storage_service();
|
|
1804
|
+
init_auth_service();
|
|
1805
|
+
import boxen6 from "boxen";
|
|
1806
|
+
|
|
1807
|
+
// src/utils/source-detector.ts
|
|
1539
1808
|
init_esm_shims();
|
|
1540
1809
|
init_errors();
|
|
1541
|
-
|
|
1810
|
+
init_storage_service();
|
|
1542
1811
|
import { exec as exec2 } from "child_process";
|
|
1543
1812
|
import { promisify as promisify2 } from "util";
|
|
1813
|
+
var execAsync2 = promisify2(exec2);
|
|
1814
|
+
var SourceDetector = class {
|
|
1815
|
+
/**
|
|
1816
|
+
* Detect all possible sources from git remotes
|
|
1817
|
+
*/
|
|
1818
|
+
async detectSources() {
|
|
1819
|
+
const detected = [];
|
|
1820
|
+
try {
|
|
1821
|
+
const { stdout } = await execAsync2("git remote -v");
|
|
1822
|
+
const remotes = this.parseRemotes(stdout);
|
|
1823
|
+
for (const remote of remotes) {
|
|
1824
|
+
const source = this.parseRemoteUrl(remote.url);
|
|
1825
|
+
if (source) {
|
|
1826
|
+
const isAuthenticated = await this.checkAuthentication(source.type);
|
|
1827
|
+
detected.push({
|
|
1828
|
+
...source,
|
|
1829
|
+
remoteUrl: remote.url,
|
|
1830
|
+
isAuthenticated
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
} catch {
|
|
1835
|
+
throw new GitError("Not a git repository");
|
|
1836
|
+
}
|
|
1837
|
+
const recommended = this.selectRecommendedSource(detected);
|
|
1838
|
+
return { detected, recommended };
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Parse git remote -v output
|
|
1842
|
+
*/
|
|
1843
|
+
parseRemotes(output) {
|
|
1844
|
+
const lines = output.split("\n").filter(Boolean);
|
|
1845
|
+
const remotes = /* @__PURE__ */ new Map();
|
|
1846
|
+
for (const line of lines) {
|
|
1847
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
|
|
1848
|
+
if (match && match[1] && match[2]) {
|
|
1849
|
+
remotes.set(match[1], match[2]);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return Array.from(remotes.entries()).map(([name, url]) => ({ name, url }));
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Parse remote URL to detect source type
|
|
1856
|
+
*/
|
|
1857
|
+
parseRemoteUrl(url) {
|
|
1858
|
+
if (url.includes("github.com")) {
|
|
1859
|
+
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
1860
|
+
if (match && match[1] && match[2]) {
|
|
1861
|
+
return {
|
|
1862
|
+
type: "github",
|
|
1863
|
+
host: "github.com",
|
|
1864
|
+
owner: match[1],
|
|
1865
|
+
repo: match[2]
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (url.includes("gitlab.com")) {
|
|
1870
|
+
const match = url.match(/gitlab\.com[:/]([^/]+)\/([^/.]+)/);
|
|
1871
|
+
if (match && match[1] && match[2]) {
|
|
1872
|
+
return {
|
|
1873
|
+
type: "gitlab",
|
|
1874
|
+
host: "gitlab.com",
|
|
1875
|
+
owner: match[1],
|
|
1876
|
+
repo: match[2]
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (url.includes("bitbucket.org")) {
|
|
1881
|
+
const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
|
|
1882
|
+
if (match && match[1] && match[2]) {
|
|
1883
|
+
return {
|
|
1884
|
+
type: "bitbucket",
|
|
1885
|
+
host: "bitbucket.org",
|
|
1886
|
+
owner: match[1],
|
|
1887
|
+
repo: match[2]
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (url.match(/\/scm\/|bitbucket\./)) {
|
|
1892
|
+
const match = url.match(/([^/:]+)[:/]scm\/([^/]+)\/([^/.]+)/);
|
|
1893
|
+
if (match && match[1] && match[2] && match[3]) {
|
|
1894
|
+
return {
|
|
1895
|
+
type: "atlassian",
|
|
1896
|
+
host: match[1],
|
|
1897
|
+
owner: match[2],
|
|
1898
|
+
repo: match[3]
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Check if user is authenticated with a specific source
|
|
1906
|
+
*/
|
|
1907
|
+
async checkAuthentication(type) {
|
|
1908
|
+
try {
|
|
1909
|
+
if (type === "github") {
|
|
1910
|
+
await execAsync2("command gh auth status");
|
|
1911
|
+
return true;
|
|
1912
|
+
} else if (type === "bitbucket" || type === "atlassian") {
|
|
1913
|
+
return await storageService.isServiceAuthenticated("bitbucket");
|
|
1914
|
+
}
|
|
1915
|
+
return false;
|
|
1916
|
+
} catch {
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Select recommended source (prefer authenticated GitHub)
|
|
1922
|
+
*/
|
|
1923
|
+
selectRecommendedSource(sources) {
|
|
1924
|
+
const authenticated = sources.find((s) => s.isAuthenticated);
|
|
1925
|
+
if (authenticated) return authenticated.type;
|
|
1926
|
+
const github = sources.find((s) => s.type === "github");
|
|
1927
|
+
if (github) return "github";
|
|
1928
|
+
return sources[0]?.type;
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
var sourceDetector = new SourceDetector();
|
|
1932
|
+
|
|
1933
|
+
// src/sync/adapter-factory.ts
|
|
1934
|
+
init_esm_shims();
|
|
1935
|
+
|
|
1936
|
+
// src/sync/github-adapter.ts
|
|
1937
|
+
init_esm_shims();
|
|
1938
|
+
|
|
1939
|
+
// src/services/github.service.ts
|
|
1940
|
+
init_esm_shims();
|
|
1941
|
+
init_errors();
|
|
1942
|
+
init_logger();
|
|
1943
|
+
import { exec as exec3 } from "child_process";
|
|
1944
|
+
import { promisify as promisify3 } from "util";
|
|
1544
1945
|
|
|
1545
1946
|
// src/services/git.service.ts
|
|
1546
1947
|
init_esm_shims();
|
|
@@ -1550,9 +1951,9 @@ import simpleGit from "simple-git";
|
|
|
1550
1951
|
init_esm_shims();
|
|
1551
1952
|
init_errors();
|
|
1552
1953
|
import { existsSync as existsSync2 } from "fs";
|
|
1553
|
-
import { join as
|
|
1954
|
+
import { join as join6 } from "path";
|
|
1554
1955
|
function validateGitRepository(path2) {
|
|
1555
|
-
const gitDir =
|
|
1956
|
+
const gitDir = join6(path2, ".git");
|
|
1556
1957
|
if (!existsSync2(gitDir)) {
|
|
1557
1958
|
throw new GitError(
|
|
1558
1959
|
"Not a git repository. Please run this command from within a git repository.",
|
|
@@ -1765,7 +2166,7 @@ var GitService = class {
|
|
|
1765
2166
|
var gitService = new GitService();
|
|
1766
2167
|
|
|
1767
2168
|
// src/services/github.service.ts
|
|
1768
|
-
var
|
|
2169
|
+
var execAsync3 = promisify3(exec3);
|
|
1769
2170
|
var GitHubService = class {
|
|
1770
2171
|
MAX_BODY_LENGTH = 5e3;
|
|
1771
2172
|
PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
|
|
@@ -1774,7 +2175,7 @@ var GitHubService = class {
|
|
|
1774
2175
|
*/
|
|
1775
2176
|
async checkGitHubCLI() {
|
|
1776
2177
|
try {
|
|
1777
|
-
await
|
|
2178
|
+
await execAsync3("command gh --version");
|
|
1778
2179
|
return true;
|
|
1779
2180
|
} catch {
|
|
1780
2181
|
return false;
|
|
@@ -1796,7 +2197,7 @@ var GitHubService = class {
|
|
|
1796
2197
|
*/
|
|
1797
2198
|
async checkAuthentication() {
|
|
1798
2199
|
try {
|
|
1799
|
-
await
|
|
2200
|
+
await execAsync3("command gh auth status");
|
|
1800
2201
|
return true;
|
|
1801
2202
|
} catch {
|
|
1802
2203
|
return false;
|
|
@@ -1821,7 +2222,7 @@ var GitHubService = class {
|
|
|
1821
2222
|
await this.ensureGitHubCLI();
|
|
1822
2223
|
await this.ensureAuthentication();
|
|
1823
2224
|
await gitService.validateRepository();
|
|
1824
|
-
const { stdout } = await
|
|
2225
|
+
const { stdout } = await execAsync3("command gh repo view --json url");
|
|
1825
2226
|
const data = JSON.parse(stdout);
|
|
1826
2227
|
if (!data.url) {
|
|
1827
2228
|
throw new GitHubError("This repository is not hosted on GitHub", {
|
|
@@ -1853,7 +2254,7 @@ var GitHubService = class {
|
|
|
1853
2254
|
async getRepositoryInfo() {
|
|
1854
2255
|
try {
|
|
1855
2256
|
await this.ensureGitHubCLI();
|
|
1856
|
-
const { stdout } = await
|
|
2257
|
+
const { stdout } = await execAsync3(
|
|
1857
2258
|
"command gh repo view --json owner,name,url,nameWithOwner"
|
|
1858
2259
|
);
|
|
1859
2260
|
const data = JSON.parse(stdout);
|
|
@@ -1878,7 +2279,7 @@ var GitHubService = class {
|
|
|
1878
2279
|
*/
|
|
1879
2280
|
async getCurrentGitHubUser() {
|
|
1880
2281
|
try {
|
|
1881
|
-
const { stdout } = await
|
|
2282
|
+
const { stdout } = await execAsync3("command gh api user --jq .login");
|
|
1882
2283
|
return stdout.trim() || null;
|
|
1883
2284
|
} catch {
|
|
1884
2285
|
logger.debug("Failed to get GitHub user");
|
|
@@ -1904,7 +2305,7 @@ var GitHubService = class {
|
|
|
1904
2305
|
const limitArg = limit ? `--limit ${limit}` : "";
|
|
1905
2306
|
const command = `command gh pr list --state merged --json ${this.PR_SEARCH_FIELDS} --search "${searchQuery}" ${limitArg}`;
|
|
1906
2307
|
logger.debug(`Running: ${command}`);
|
|
1907
|
-
const { stdout } = await
|
|
2308
|
+
const { stdout } = await execAsync3(command);
|
|
1908
2309
|
const prs = JSON.parse(stdout);
|
|
1909
2310
|
logger.debug(`Found ${prs.length} merged PRs`);
|
|
1910
2311
|
return prs;
|
|
@@ -1962,189 +2363,503 @@ ${truncatedBody}` : title;
|
|
|
1962
2363
|
};
|
|
1963
2364
|
var githubService = new GitHubService();
|
|
1964
2365
|
|
|
1965
|
-
// src/
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2366
|
+
// src/sync/github-adapter.ts
|
|
2367
|
+
var GitHubSyncAdapter = class {
|
|
2368
|
+
name = "github";
|
|
2369
|
+
async validate() {
|
|
2370
|
+
await githubService.validateGitHubRepository();
|
|
2371
|
+
}
|
|
2372
|
+
async getRepositoryInfo() {
|
|
2373
|
+
const info = await githubService.getRepositoryInfo();
|
|
2374
|
+
return {
|
|
2375
|
+
owner: info.owner,
|
|
2376
|
+
name: info.name,
|
|
2377
|
+
fullName: info.fullName,
|
|
2378
|
+
url: info.url
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
async fetchWorkItems(options) {
|
|
2382
|
+
let prs;
|
|
2383
|
+
if (options.author) {
|
|
2384
|
+
prs = await githubService.getMergedPRs({
|
|
2385
|
+
days: options.days,
|
|
2386
|
+
limit: options.limit,
|
|
2387
|
+
author: options.author
|
|
2388
|
+
});
|
|
2389
|
+
} else {
|
|
2390
|
+
prs = await githubService.getPRsByCurrentUser({
|
|
2391
|
+
days: options.days,
|
|
2392
|
+
limit: options.limit
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
return prs.map((pr) => githubService.transformPRToCommit(pr));
|
|
2396
|
+
}
|
|
2397
|
+
async isAuthenticated() {
|
|
2398
|
+
try {
|
|
2399
|
+
await githubService.validateGitHubRepository();
|
|
2400
|
+
return true;
|
|
2401
|
+
} catch {
|
|
2402
|
+
return false;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
async getCurrentUser() {
|
|
2406
|
+
return githubService.getCurrentGitHubUser();
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
var githubSyncAdapter = new GitHubSyncAdapter();
|
|
1981
2410
|
|
|
1982
|
-
// src/
|
|
2411
|
+
// src/sync/bitbucket-adapter.ts
|
|
1983
2412
|
init_esm_shims();
|
|
1984
|
-
import Table from "cli-table3";
|
|
1985
|
-
import terminalLink from "terminal-link";
|
|
1986
2413
|
|
|
1987
|
-
// src/
|
|
2414
|
+
// src/services/bitbucket.service.ts
|
|
1988
2415
|
init_esm_shims();
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
warningBold: chalk5.yellow.bold,
|
|
1999
|
-
// Error states
|
|
2000
|
-
error: chalk5.red,
|
|
2001
|
-
errorBold: chalk5.red.bold,
|
|
2002
|
-
// Info and metadata
|
|
2003
|
-
info: chalk5.gray,
|
|
2004
|
-
infoDim: chalk5.dim,
|
|
2005
|
-
// Highlighted/important data
|
|
2006
|
-
highlight: chalk5.yellow.bold,
|
|
2007
|
-
highlightCyan: chalk5.cyan.bold,
|
|
2008
|
-
// Text emphasis
|
|
2009
|
-
bold: chalk5.bold,
|
|
2010
|
-
dim: chalk5.dim,
|
|
2011
|
-
// Special purpose
|
|
2012
|
-
white: chalk5.white,
|
|
2013
|
-
gray: chalk5.gray,
|
|
2014
|
-
// Links and URLs
|
|
2015
|
-
link: chalk5.blue.underline
|
|
2016
|
-
};
|
|
2017
|
-
var theme = {
|
|
2018
|
-
/**
|
|
2019
|
-
* Format command names and CLI actions
|
|
2020
|
-
*/
|
|
2021
|
-
command: (text) => colors.primary(text),
|
|
2416
|
+
init_errors();
|
|
2417
|
+
init_logger();
|
|
2418
|
+
init_storage_service();
|
|
2419
|
+
import { exec as exec4 } from "child_process";
|
|
2420
|
+
import { promisify as promisify4 } from "util";
|
|
2421
|
+
var execAsync4 = promisify4(exec4);
|
|
2422
|
+
var BitbucketService = class {
|
|
2423
|
+
BITBUCKET_API_BASE = "https://api.bitbucket.org/2.0";
|
|
2424
|
+
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
2022
2425
|
/**
|
|
2023
|
-
*
|
|
2426
|
+
* Get stored Bitbucket credentials
|
|
2024
2427
|
*/
|
|
2025
|
-
|
|
2026
|
-
|
|
2428
|
+
async getCredentials() {
|
|
2429
|
+
const creds = await storageService.getServiceCredentials("bitbucket");
|
|
2430
|
+
if (!creds || !creds.username || !creds.accessToken) {
|
|
2431
|
+
throw new BitbucketError("Not authenticated with Bitbucket", {
|
|
2432
|
+
hint: "Run: bragduck auth bitbucket"
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
if (creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
2436
|
+
throw new BitbucketError("API token has expired", {
|
|
2437
|
+
hint: "Run: bragduck auth bitbucket"
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
return {
|
|
2441
|
+
email: creds.username,
|
|
2442
|
+
// username field stores email
|
|
2443
|
+
apiToken: creds.accessToken
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2027
2446
|
/**
|
|
2028
|
-
*
|
|
2447
|
+
* Make authenticated request to Bitbucket API
|
|
2029
2448
|
*/
|
|
2030
|
-
|
|
2031
|
-
|
|
2449
|
+
async request(endpoint) {
|
|
2450
|
+
const { email, apiToken } = await this.getCredentials();
|
|
2451
|
+
const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
|
|
2452
|
+
logger.debug(`Bitbucket API: GET ${endpoint}`);
|
|
2453
|
+
const response = await fetch(`${this.BITBUCKET_API_BASE}${endpoint}`, {
|
|
2454
|
+
headers: {
|
|
2455
|
+
Authorization: `Basic ${auth}`,
|
|
2456
|
+
Accept: "application/json"
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
if (!response.ok) {
|
|
2460
|
+
const statusText = response.statusText;
|
|
2461
|
+
const status = response.status;
|
|
2462
|
+
if (status === 401) {
|
|
2463
|
+
throw new BitbucketError("Invalid or expired API token", {
|
|
2464
|
+
hint: "Run: bragduck auth bitbucket",
|
|
2465
|
+
originalError: statusText
|
|
2466
|
+
});
|
|
2467
|
+
} else if (status === 403) {
|
|
2468
|
+
throw new BitbucketError("Forbidden - check token permissions", {
|
|
2469
|
+
hint: "Token needs: pullrequest:read, repository:read, account:read",
|
|
2470
|
+
originalError: statusText
|
|
2471
|
+
});
|
|
2472
|
+
} else if (status === 404) {
|
|
2473
|
+
throw new BitbucketError("Repository or resource not found", {
|
|
2474
|
+
originalError: statusText
|
|
2475
|
+
});
|
|
2476
|
+
} else if (status === 429) {
|
|
2477
|
+
throw new BitbucketError("Rate limit exceeded", {
|
|
2478
|
+
hint: "Wait a few minutes before trying again",
|
|
2479
|
+
originalError: statusText
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
throw new BitbucketError(`API request failed: ${statusText}`, {
|
|
2483
|
+
originalError: statusText
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
return response.json();
|
|
2487
|
+
}
|
|
2032
2488
|
/**
|
|
2033
|
-
*
|
|
2489
|
+
* Extract workspace and repo from git remote URL
|
|
2034
2490
|
*/
|
|
2035
|
-
|
|
2491
|
+
parseRemoteUrl(url) {
|
|
2492
|
+
const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
|
|
2493
|
+
if (match && match[1] && match[2]) {
|
|
2494
|
+
return {
|
|
2495
|
+
workspace: match[1],
|
|
2496
|
+
repo: match[2].replace(/\.git$/, "")
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2036
2501
|
/**
|
|
2037
|
-
*
|
|
2502
|
+
* Get repository info from git remote
|
|
2038
2503
|
*/
|
|
2039
|
-
|
|
2504
|
+
async getRepoFromGit() {
|
|
2505
|
+
try {
|
|
2506
|
+
const { stdout } = await execAsync4("command git remote get-url origin");
|
|
2507
|
+
const remoteUrl = stdout.trim();
|
|
2508
|
+
const parsed = this.parseRemoteUrl(remoteUrl);
|
|
2509
|
+
if (!parsed) {
|
|
2510
|
+
throw new BitbucketError("Could not parse Bitbucket repository from git remote", {
|
|
2511
|
+
hint: "Ensure this is a Bitbucket repository"
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
return parsed;
|
|
2515
|
+
} catch (error) {
|
|
2516
|
+
if (error instanceof BitbucketError) {
|
|
2517
|
+
throw error;
|
|
2518
|
+
}
|
|
2519
|
+
throw new GitError("Could not get git remote URL");
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2040
2522
|
/**
|
|
2041
|
-
*
|
|
2523
|
+
* Validate that this is a Bitbucket repository and credentials work
|
|
2042
2524
|
*/
|
|
2043
|
-
|
|
2525
|
+
async validateBitbucketRepository() {
|
|
2526
|
+
await gitService.validateRepository();
|
|
2527
|
+
const { workspace, repo } = await this.getRepoFromGit();
|
|
2528
|
+
try {
|
|
2529
|
+
await this.request(`/repositories/${workspace}/${repo}`);
|
|
2530
|
+
} catch (error) {
|
|
2531
|
+
if (error instanceof BitbucketError) {
|
|
2532
|
+
throw error;
|
|
2533
|
+
}
|
|
2534
|
+
throw new BitbucketError("Could not access Bitbucket repository via API", {
|
|
2535
|
+
hint: "Check that this is a Bitbucket repository and your credentials are valid",
|
|
2536
|
+
originalError: error instanceof Error ? error.message : String(error)
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2044
2540
|
/**
|
|
2045
|
-
*
|
|
2541
|
+
* Get repository information
|
|
2046
2542
|
*/
|
|
2047
|
-
|
|
2543
|
+
async getRepositoryInfo() {
|
|
2544
|
+
const { workspace, repo } = await this.getRepoFromGit();
|
|
2545
|
+
const bitbucketRepo = await this.request(
|
|
2546
|
+
`/repositories/${workspace}/${repo}`
|
|
2547
|
+
);
|
|
2548
|
+
return {
|
|
2549
|
+
owner: workspace,
|
|
2550
|
+
name: bitbucketRepo.name,
|
|
2551
|
+
fullName: bitbucketRepo.full_name,
|
|
2552
|
+
url: bitbucketRepo.links.html.href
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2048
2555
|
/**
|
|
2049
|
-
*
|
|
2556
|
+
* Get current user's account ID
|
|
2050
2557
|
*/
|
|
2051
|
-
|
|
2558
|
+
async getCurrentUserAccountId() {
|
|
2559
|
+
const user = await this.request("/user");
|
|
2560
|
+
return user.account_id;
|
|
2561
|
+
}
|
|
2052
2562
|
/**
|
|
2053
|
-
*
|
|
2563
|
+
* Get current user's username (nickname)
|
|
2054
2564
|
*/
|
|
2055
|
-
|
|
2565
|
+
async getCurrentGitHubUser() {
|
|
2566
|
+
try {
|
|
2567
|
+
const user = await this.request("/user");
|
|
2568
|
+
return user.nickname;
|
|
2569
|
+
} catch {
|
|
2570
|
+
return null;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2056
2573
|
/**
|
|
2057
|
-
*
|
|
2574
|
+
* Fetch merged pull requests with optional filtering
|
|
2058
2575
|
*/
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2576
|
+
async getMergedPRs(options = {}) {
|
|
2577
|
+
const { workspace, repo } = await this.getRepoFromGit();
|
|
2578
|
+
const queries = ['state="MERGED"'];
|
|
2579
|
+
if (options.days) {
|
|
2580
|
+
const since = /* @__PURE__ */ new Date();
|
|
2581
|
+
since.setDate(since.getDate() - options.days);
|
|
2582
|
+
queries.push(`updated_on>=${since.toISOString()}`);
|
|
2583
|
+
}
|
|
2584
|
+
if (options.author) {
|
|
2585
|
+
queries.push(`author.account_id="${options.author}"`);
|
|
2586
|
+
}
|
|
2587
|
+
const queryString = queries.join(" AND ");
|
|
2588
|
+
const allPRs = [];
|
|
2589
|
+
let endpoint = `/repositories/${workspace}/${repo}/pullrequests?q=${encodeURIComponent(queryString)}&pagelen=100`;
|
|
2590
|
+
while (endpoint) {
|
|
2591
|
+
const response = await this.request(endpoint);
|
|
2592
|
+
allPRs.push(...response.values);
|
|
2593
|
+
logger.debug(
|
|
2594
|
+
`Fetched ${response.values.length} PRs (total: ${allPRs.length})${response.next ? ", fetching next page..." : ""}`
|
|
2595
|
+
);
|
|
2596
|
+
if (options.limit && allPRs.length >= options.limit) {
|
|
2597
|
+
return allPRs.slice(0, options.limit);
|
|
2598
|
+
}
|
|
2599
|
+
if (response.next) {
|
|
2600
|
+
const url = new URL(response.next);
|
|
2601
|
+
endpoint = url.pathname + url.search;
|
|
2602
|
+
} else {
|
|
2603
|
+
endpoint = "";
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
return allPRs;
|
|
2607
|
+
}
|
|
2064
2608
|
/**
|
|
2065
|
-
*
|
|
2609
|
+
* Fetch PRs for the current authenticated user
|
|
2066
2610
|
*/
|
|
2067
|
-
|
|
2611
|
+
async getPRsByCurrentUser(options = {}) {
|
|
2612
|
+
const accountId = await this.getCurrentUserAccountId();
|
|
2613
|
+
return this.getMergedPRs({
|
|
2614
|
+
...options,
|
|
2615
|
+
author: accountId
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2068
2618
|
/**
|
|
2069
|
-
*
|
|
2619
|
+
* Transform Bitbucket PR to GitCommit format
|
|
2070
2620
|
*/
|
|
2071
|
-
|
|
2621
|
+
transformPRToCommit(pr) {
|
|
2622
|
+
let message = pr.title;
|
|
2623
|
+
if (pr.description) {
|
|
2624
|
+
const truncatedDesc = pr.description.substring(0, this.MAX_DESCRIPTION_LENGTH);
|
|
2625
|
+
message = `${pr.title}
|
|
2626
|
+
|
|
2627
|
+
${truncatedDesc}`;
|
|
2628
|
+
}
|
|
2629
|
+
return {
|
|
2630
|
+
sha: `pr-${pr.id}`,
|
|
2631
|
+
message,
|
|
2632
|
+
author: pr.author.nickname,
|
|
2633
|
+
authorEmail: "",
|
|
2634
|
+
// Not available in Bitbucket API
|
|
2635
|
+
date: pr.created_on,
|
|
2636
|
+
url: pr.links.html.href,
|
|
2637
|
+
diffStats: {
|
|
2638
|
+
filesChanged: 0,
|
|
2639
|
+
// Would require separate API call to diffstat endpoint
|
|
2640
|
+
insertions: 0,
|
|
2641
|
+
deletions: 0
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
var bitbucketService = new BitbucketService();
|
|
2647
|
+
|
|
2648
|
+
// src/sync/bitbucket-adapter.ts
|
|
2649
|
+
var BitbucketSyncAdapter = class {
|
|
2650
|
+
name = "bitbucket";
|
|
2651
|
+
async validate() {
|
|
2652
|
+
await bitbucketService.validateBitbucketRepository();
|
|
2653
|
+
}
|
|
2654
|
+
async getRepositoryInfo() {
|
|
2655
|
+
const info = await bitbucketService.getRepositoryInfo();
|
|
2656
|
+
return {
|
|
2657
|
+
owner: info.owner,
|
|
2658
|
+
name: info.name,
|
|
2659
|
+
fullName: info.fullName,
|
|
2660
|
+
url: info.url
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
async fetchWorkItems(options) {
|
|
2664
|
+
let prs;
|
|
2665
|
+
if (options.author) {
|
|
2666
|
+
prs = await bitbucketService.getMergedPRs({
|
|
2667
|
+
days: options.days,
|
|
2668
|
+
limit: options.limit,
|
|
2669
|
+
author: options.author
|
|
2670
|
+
});
|
|
2671
|
+
} else {
|
|
2672
|
+
prs = await bitbucketService.getPRsByCurrentUser({
|
|
2673
|
+
days: options.days,
|
|
2674
|
+
limit: options.limit
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
return prs.map((pr) => bitbucketService.transformPRToCommit(pr));
|
|
2678
|
+
}
|
|
2679
|
+
async isAuthenticated() {
|
|
2680
|
+
try {
|
|
2681
|
+
await bitbucketService.validateBitbucketRepository();
|
|
2682
|
+
return true;
|
|
2683
|
+
} catch {
|
|
2684
|
+
return false;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
async getCurrentUser() {
|
|
2688
|
+
return bitbucketService.getCurrentGitHubUser();
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
var bitbucketSyncAdapter = new BitbucketSyncAdapter();
|
|
2692
|
+
|
|
2693
|
+
// src/sync/adapter-factory.ts
|
|
2694
|
+
var AdapterFactory = class {
|
|
2072
2695
|
/**
|
|
2073
|
-
*
|
|
2696
|
+
* Get adapter for a specific source type
|
|
2074
2697
|
*/
|
|
2075
|
-
|
|
2698
|
+
static getAdapter(source) {
|
|
2699
|
+
switch (source) {
|
|
2700
|
+
case "github":
|
|
2701
|
+
return githubSyncAdapter;
|
|
2702
|
+
case "bitbucket":
|
|
2703
|
+
case "atlassian":
|
|
2704
|
+
return bitbucketSyncAdapter;
|
|
2705
|
+
// Bitbucket Cloud and Server use same adapter
|
|
2706
|
+
case "gitlab":
|
|
2707
|
+
throw new Error(`${source} adapter not yet implemented`);
|
|
2708
|
+
default:
|
|
2709
|
+
throw new Error(`Unknown source type: ${source}`);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2076
2712
|
/**
|
|
2077
|
-
*
|
|
2713
|
+
* Check if adapter is available for source
|
|
2078
2714
|
*/
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
var boxStyles = {
|
|
2082
|
-
success: {
|
|
2083
|
-
borderColor: "green",
|
|
2084
|
-
borderStyle: "round",
|
|
2085
|
-
padding: 1,
|
|
2086
|
-
margin: 1
|
|
2087
|
-
},
|
|
2088
|
-
error: {
|
|
2089
|
-
borderColor: "red",
|
|
2090
|
-
borderStyle: "round",
|
|
2091
|
-
padding: 1,
|
|
2092
|
-
margin: 1
|
|
2093
|
-
},
|
|
2094
|
-
warning: {
|
|
2095
|
-
borderColor: "yellow",
|
|
2096
|
-
borderStyle: "round",
|
|
2097
|
-
padding: 1,
|
|
2098
|
-
margin: 1
|
|
2099
|
-
},
|
|
2100
|
-
info: {
|
|
2101
|
-
borderColor: "cyan",
|
|
2102
|
-
borderStyle: "round",
|
|
2103
|
-
padding: 1,
|
|
2104
|
-
margin: 1
|
|
2105
|
-
},
|
|
2106
|
-
plain: {
|
|
2107
|
-
borderColor: "gray",
|
|
2108
|
-
borderStyle: "round",
|
|
2109
|
-
padding: 1,
|
|
2110
|
-
margin: 1
|
|
2715
|
+
static isSupported(source) {
|
|
2716
|
+
return source === "github" || source === "bitbucket" || source === "atlassian";
|
|
2111
2717
|
}
|
|
2112
2718
|
};
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2719
|
+
|
|
2720
|
+
// src/commands/sync.ts
|
|
2721
|
+
init_logger();
|
|
2722
|
+
|
|
2723
|
+
// src/utils/auth-helper.ts
|
|
2724
|
+
init_esm_shims();
|
|
2725
|
+
init_auth_service();
|
|
2726
|
+
import boxen5 from "boxen";
|
|
2727
|
+
|
|
2728
|
+
// src/commands/init.ts
|
|
2729
|
+
init_esm_shims();
|
|
2730
|
+
init_auth_service();
|
|
2731
|
+
init_logger();
|
|
2732
|
+
import ora from "ora";
|
|
2733
|
+
import boxen3 from "boxen";
|
|
2734
|
+
import chalk5 from "chalk";
|
|
2735
|
+
async function initCommand() {
|
|
2736
|
+
logger.log("");
|
|
2737
|
+
logger.log(
|
|
2738
|
+
boxen3(
|
|
2739
|
+
chalk5.yellow.bold("\u26A0 Deprecation Notice") + `
|
|
2740
|
+
|
|
2741
|
+
The ${chalk5.cyan("init")} command is deprecated.
|
|
2742
|
+
Please use ${chalk5.cyan("bragduck auth login")} instead.
|
|
2743
|
+
|
|
2744
|
+
` + chalk5.dim("This command will be removed in v3.0.0"),
|
|
2745
|
+
{
|
|
2746
|
+
padding: 1,
|
|
2747
|
+
borderStyle: "round",
|
|
2748
|
+
borderColor: "yellow",
|
|
2749
|
+
dimBorder: true
|
|
2750
|
+
}
|
|
2751
|
+
)
|
|
2752
|
+
);
|
|
2753
|
+
logger.log("");
|
|
2754
|
+
logger.info("Starting authentication flow...");
|
|
2755
|
+
logger.log("");
|
|
2756
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
2757
|
+
if (isAuthenticated) {
|
|
2758
|
+
const userInfo = authService.getUserInfo();
|
|
2759
|
+
if (userInfo) {
|
|
2760
|
+
logger.log(
|
|
2761
|
+
boxen3(
|
|
2762
|
+
`${chalk5.yellow("Already authenticated!")}
|
|
2763
|
+
|
|
2764
|
+
${chalk5.gray("User:")} ${userInfo.name}
|
|
2765
|
+
${chalk5.gray("Email:")} ${userInfo.email}
|
|
2766
|
+
|
|
2767
|
+
${chalk5.dim("Run")} ${chalk5.cyan("bragduck logout")} ${chalk5.dim("to sign out")}`,
|
|
2768
|
+
{
|
|
2769
|
+
padding: 1,
|
|
2770
|
+
margin: 1,
|
|
2771
|
+
borderStyle: "round",
|
|
2772
|
+
borderColor: "yellow"
|
|
2773
|
+
}
|
|
2774
|
+
)
|
|
2775
|
+
);
|
|
2776
|
+
logger.log("");
|
|
2777
|
+
globalThis.setTimeout(() => {
|
|
2778
|
+
process.exit(0);
|
|
2779
|
+
}, 100);
|
|
2780
|
+
return;
|
|
2125
2781
|
}
|
|
2126
2782
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2783
|
+
const spinner = ora("Opening browser for authentication...").start();
|
|
2784
|
+
try {
|
|
2785
|
+
spinner.text = "Waiting for authentication...";
|
|
2786
|
+
const userInfo = await authService.login();
|
|
2787
|
+
spinner.succeed("Authentication successful!");
|
|
2788
|
+
logger.log("");
|
|
2789
|
+
logger.log(
|
|
2790
|
+
boxen3(
|
|
2791
|
+
`${chalk5.green.bold("\u2713 Successfully authenticated!")}
|
|
2792
|
+
|
|
2793
|
+
${chalk5.gray("Welcome,")} ${chalk5.cyan(userInfo.name)}
|
|
2794
|
+
${chalk5.gray("Email:")} ${userInfo.email}
|
|
2795
|
+
|
|
2796
|
+
${chalk5.dim("You can now use")} ${chalk5.cyan("bragduck scan")} ${chalk5.dim("to create brags!")}`,
|
|
2797
|
+
{
|
|
2798
|
+
padding: 1,
|
|
2799
|
+
margin: 1,
|
|
2800
|
+
borderStyle: "round",
|
|
2801
|
+
borderColor: "green"
|
|
2802
|
+
}
|
|
2803
|
+
)
|
|
2804
|
+
);
|
|
2805
|
+
logger.log("");
|
|
2806
|
+
globalThis.setTimeout(() => {
|
|
2807
|
+
process.exit(0);
|
|
2808
|
+
}, 100);
|
|
2809
|
+
return;
|
|
2810
|
+
} catch (error) {
|
|
2811
|
+
spinner.fail("Authentication failed");
|
|
2812
|
+
logger.log("");
|
|
2813
|
+
const err = error;
|
|
2814
|
+
logger.log(
|
|
2815
|
+
boxen3(
|
|
2816
|
+
`${chalk5.red.bold("\u2717 Authentication Failed")}
|
|
2817
|
+
|
|
2818
|
+
${err.message}
|
|
2819
|
+
|
|
2820
|
+
${chalk5.dim("Hint:")} ${getErrorHint(err)}`,
|
|
2821
|
+
{
|
|
2822
|
+
padding: 1,
|
|
2823
|
+
margin: 1,
|
|
2824
|
+
borderStyle: "round",
|
|
2825
|
+
borderColor: "red"
|
|
2826
|
+
}
|
|
2827
|
+
)
|
|
2828
|
+
);
|
|
2829
|
+
process.exit(1);
|
|
2141
2830
|
}
|
|
2142
2831
|
}
|
|
2143
|
-
function
|
|
2144
|
-
|
|
2832
|
+
function getErrorHint(error) {
|
|
2833
|
+
if (error.name === "OAuthError") {
|
|
2834
|
+
if (error.message.includes("timeout")) {
|
|
2835
|
+
return "Try again and complete the authentication within 2 minutes";
|
|
2836
|
+
}
|
|
2837
|
+
if (error.message.includes("CSRF")) {
|
|
2838
|
+
return "This might be a security issue. Try running the command again";
|
|
2839
|
+
}
|
|
2840
|
+
return "Check your internet connection and try again";
|
|
2841
|
+
}
|
|
2842
|
+
if (error.name === "NetworkError") {
|
|
2843
|
+
return "Check your internet connection and firewall settings";
|
|
2844
|
+
}
|
|
2845
|
+
if (error.name === "AuthenticationError") {
|
|
2846
|
+
return "Verify your credentials and try again";
|
|
2847
|
+
}
|
|
2848
|
+
return "Try running the command again or check the logs with DEBUG=* bragduck init";
|
|
2145
2849
|
}
|
|
2146
2850
|
|
|
2851
|
+
// src/utils/auth-helper.ts
|
|
2852
|
+
init_logger();
|
|
2853
|
+
|
|
2854
|
+
// src/ui/prompts.ts
|
|
2855
|
+
init_esm_shims();
|
|
2856
|
+
import { checkbox, confirm, input as input2, select, editor } from "@inquirer/prompts";
|
|
2857
|
+
import boxen4 from "boxen";
|
|
2858
|
+
|
|
2147
2859
|
// src/ui/formatters.ts
|
|
2860
|
+
init_esm_shims();
|
|
2861
|
+
import Table from "cli-table3";
|
|
2862
|
+
import terminalLink from "terminal-link";
|
|
2148
2863
|
function formatCommitChoice(commit) {
|
|
2149
2864
|
let displaySha;
|
|
2150
2865
|
if (commit.sha.startsWith("pr-")) {
|
|
@@ -2281,7 +2996,7 @@ async function promptSelectCommits(commits) {
|
|
|
2281
2996
|
return selected;
|
|
2282
2997
|
}
|
|
2283
2998
|
async function promptConfirm(message, defaultValue = true) {
|
|
2284
|
-
return await
|
|
2999
|
+
return await confirm({
|
|
2285
3000
|
message,
|
|
2286
3001
|
default: defaultValue
|
|
2287
3002
|
});
|
|
@@ -2300,7 +3015,7 @@ async function promptDaysToScan(defaultDays = 30) {
|
|
|
2300
3015
|
default: "30"
|
|
2301
3016
|
});
|
|
2302
3017
|
if (selected === "custom") {
|
|
2303
|
-
const customDays = await
|
|
3018
|
+
const customDays = await input2({
|
|
2304
3019
|
message: "Enter number of days:",
|
|
2305
3020
|
default: defaultDays.toString(),
|
|
2306
3021
|
validate: (value) => {
|
|
@@ -2420,123 +3135,465 @@ ${theme.label("PR Link")} ${colors.link(prUrl)}`;
|
|
|
2420
3135
|
for (let j = i + 1; j < refinedBrags.length; j++) {
|
|
2421
3136
|
acceptedBrags.push(refinedBrags[j]);
|
|
2422
3137
|
}
|
|
2423
|
-
reviewingBrag = false;
|
|
2424
|
-
i = refinedBrags.length - 1;
|
|
2425
|
-
break;
|
|
2426
|
-
}
|
|
2427
|
-
if (action === "skip") {
|
|
2428
|
-
reviewingBrag = false;
|
|
2429
|
-
continue;
|
|
2430
|
-
}
|
|
2431
|
-
if (action === "accept") {
|
|
2432
|
-
acceptedBrags.push(currentBrag);
|
|
2433
|
-
reviewingBrag = false;
|
|
2434
|
-
continue;
|
|
2435
|
-
}
|
|
2436
|
-
let editedBrag = { ...currentBrag };
|
|
2437
|
-
if (action === "edit-title" || action === "edit-both") {
|
|
2438
|
-
console.log("");
|
|
2439
|
-
const newTitle = await
|
|
2440
|
-
message: "Enter new title:",
|
|
2441
|
-
default: currentBrag.refined_title
|
|
2442
|
-
});
|
|
2443
|
-
editedBrag.refined_title = newTitle;
|
|
2444
|
-
}
|
|
2445
|
-
if (action === "edit-desc" || action === "edit-both") {
|
|
2446
|
-
console.log("");
|
|
2447
|
-
const newDesc = await editor({
|
|
2448
|
-
message: "Edit description (will open your default editor):",
|
|
2449
|
-
default: currentBrag.refined_description
|
|
2450
|
-
});
|
|
2451
|
-
editedBrag.refined_description = newDesc;
|
|
3138
|
+
reviewingBrag = false;
|
|
3139
|
+
i = refinedBrags.length - 1;
|
|
3140
|
+
break;
|
|
3141
|
+
}
|
|
3142
|
+
if (action === "skip") {
|
|
3143
|
+
reviewingBrag = false;
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
if (action === "accept") {
|
|
3147
|
+
acceptedBrags.push(currentBrag);
|
|
3148
|
+
reviewingBrag = false;
|
|
3149
|
+
continue;
|
|
3150
|
+
}
|
|
3151
|
+
let editedBrag = { ...currentBrag };
|
|
3152
|
+
if (action === "edit-title" || action === "edit-both") {
|
|
3153
|
+
console.log("");
|
|
3154
|
+
const newTitle = await input2({
|
|
3155
|
+
message: "Enter new title:",
|
|
3156
|
+
default: currentBrag.refined_title
|
|
3157
|
+
});
|
|
3158
|
+
editedBrag.refined_title = newTitle;
|
|
3159
|
+
}
|
|
3160
|
+
if (action === "edit-desc" || action === "edit-both") {
|
|
3161
|
+
console.log("");
|
|
3162
|
+
const newDesc = await editor({
|
|
3163
|
+
message: "Edit description (will open your default editor):",
|
|
3164
|
+
default: currentBrag.refined_description
|
|
3165
|
+
});
|
|
3166
|
+
editedBrag.refined_description = newDesc;
|
|
3167
|
+
}
|
|
3168
|
+
currentBrag = editedBrag;
|
|
3169
|
+
console.log("\n" + theme.success("\u2713 Changes saved. Review your edits:") + "\n");
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return acceptedBrags;
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
// src/utils/auth-helper.ts
|
|
3176
|
+
async function ensureAuthenticated() {
|
|
3177
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
3178
|
+
if (isAuthenticated) {
|
|
3179
|
+
return true;
|
|
3180
|
+
}
|
|
3181
|
+
logger.log("");
|
|
3182
|
+
logger.log(
|
|
3183
|
+
boxen5(
|
|
3184
|
+
theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
|
|
3185
|
+
boxStyles.warning
|
|
3186
|
+
)
|
|
3187
|
+
);
|
|
3188
|
+
logger.log("");
|
|
3189
|
+
const shouldAuth = await promptConfirm("Authenticate now?", true);
|
|
3190
|
+
if (!shouldAuth) {
|
|
3191
|
+
logger.log("");
|
|
3192
|
+
logger.info(
|
|
3193
|
+
theme.secondary("Authentication skipped. Run ") + theme.command("bragduck init") + theme.secondary(" when you're ready to authenticate.")
|
|
3194
|
+
);
|
|
3195
|
+
logger.log("");
|
|
3196
|
+
return false;
|
|
3197
|
+
}
|
|
3198
|
+
try {
|
|
3199
|
+
await initCommand();
|
|
3200
|
+
return true;
|
|
3201
|
+
} catch {
|
|
3202
|
+
return false;
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
// src/ui/spinners.ts
|
|
3207
|
+
init_esm_shims();
|
|
3208
|
+
import ora2 from "ora";
|
|
3209
|
+
function createSpinner(text) {
|
|
3210
|
+
return ora2({
|
|
3211
|
+
text,
|
|
3212
|
+
color: "cyan",
|
|
3213
|
+
spinner: "dots"
|
|
3214
|
+
});
|
|
3215
|
+
}
|
|
3216
|
+
function createStepSpinner(currentStep, totalSteps, text) {
|
|
3217
|
+
const stepIndicator = theme.step(currentStep, totalSteps);
|
|
3218
|
+
return ora2({
|
|
3219
|
+
text: `${stepIndicator} ${text}`,
|
|
3220
|
+
color: "cyan",
|
|
3221
|
+
spinner: "dots"
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
function fetchingBragsSpinner() {
|
|
3225
|
+
return createSpinner("Fetching your brags...");
|
|
3226
|
+
}
|
|
3227
|
+
function succeedSpinner(spinner, text) {
|
|
3228
|
+
if (text) {
|
|
3229
|
+
spinner.succeed(colors.success(text));
|
|
3230
|
+
} else {
|
|
3231
|
+
spinner.succeed();
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function failSpinner(spinner, text) {
|
|
3235
|
+
if (text) {
|
|
3236
|
+
spinner.fail(colors.error(text));
|
|
3237
|
+
} else {
|
|
3238
|
+
spinner.fail();
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
function succeedStepSpinner(spinner, currentStep, totalSteps, text) {
|
|
3242
|
+
const stepIndicator = theme.step(currentStep, totalSteps);
|
|
3243
|
+
spinner.succeed(`${stepIndicator} ${colors.success(text)}`);
|
|
3244
|
+
}
|
|
3245
|
+
function failStepSpinner(spinner, currentStep, totalSteps, text) {
|
|
3246
|
+
const stepIndicator = theme.step(currentStep, totalSteps);
|
|
3247
|
+
spinner.fail(`${stepIndicator} ${colors.error(text)}`);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// src/commands/sync.ts
|
|
3251
|
+
async function syncCommand(options = {}) {
|
|
3252
|
+
logger.log("");
|
|
3253
|
+
const TOTAL_STEPS = 5;
|
|
3254
|
+
try {
|
|
3255
|
+
const isAuthenticated = await ensureAuthenticated();
|
|
3256
|
+
if (!isAuthenticated) {
|
|
3257
|
+
process.exit(1);
|
|
3258
|
+
}
|
|
3259
|
+
logger.debug("Fetching subscription status...");
|
|
3260
|
+
const subscriptionStatus = await apiService.getSubscriptionStatus();
|
|
3261
|
+
logger.debug("Subscription status response:", JSON.stringify(subscriptionStatus, null, 2));
|
|
3262
|
+
if (subscriptionStatus.tier === "FREE") {
|
|
3263
|
+
logger.debug("FREE tier detected - blocking sync command");
|
|
3264
|
+
logger.log("");
|
|
3265
|
+
logger.log(
|
|
3266
|
+
boxen6(
|
|
3267
|
+
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"),
|
|
3268
|
+
{
|
|
3269
|
+
...boxStyles.warning,
|
|
3270
|
+
padding: 1,
|
|
3271
|
+
margin: 1
|
|
3272
|
+
}
|
|
3273
|
+
)
|
|
3274
|
+
);
|
|
3275
|
+
logger.log("");
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with sync`);
|
|
3279
|
+
const detectionSpinner = createStepSpinner(1, TOTAL_STEPS, "Detecting repository source");
|
|
3280
|
+
detectionSpinner.start();
|
|
3281
|
+
const detectionResult = await sourceDetector.detectSources();
|
|
3282
|
+
if (detectionResult.detected.length === 0) {
|
|
3283
|
+
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "No supported sources detected");
|
|
3284
|
+
logger.log("");
|
|
3285
|
+
logger.info("Make sure you are in a git repository with a remote URL");
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
const sourceType = options.source || detectionResult.recommended;
|
|
3289
|
+
if (!sourceType) {
|
|
3290
|
+
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "Could not determine source");
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
if (!AdapterFactory.isSupported(sourceType)) {
|
|
3294
|
+
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source ${sourceType} not yet supported`);
|
|
3295
|
+
logger.log("");
|
|
3296
|
+
logger.info(`Currently supported: GitHub`);
|
|
3297
|
+
logger.info(`Coming soon: GitLab, Atlassian, Bitbucket`);
|
|
3298
|
+
return;
|
|
3299
|
+
}
|
|
3300
|
+
succeedStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source: ${theme.value(sourceType)}`);
|
|
3301
|
+
logger.log("");
|
|
3302
|
+
const adapter = AdapterFactory.getAdapter(sourceType);
|
|
3303
|
+
const repoSpinner = createStepSpinner(2, TOTAL_STEPS, "Validating repository");
|
|
3304
|
+
repoSpinner.start();
|
|
3305
|
+
await adapter.validate();
|
|
3306
|
+
const repoInfo = await adapter.getRepositoryInfo();
|
|
3307
|
+
succeedStepSpinner(
|
|
3308
|
+
repoSpinner,
|
|
3309
|
+
2,
|
|
3310
|
+
TOTAL_STEPS,
|
|
3311
|
+
`Repository: ${theme.value(repoInfo.fullName)}`
|
|
3312
|
+
);
|
|
3313
|
+
logger.log("");
|
|
3314
|
+
let days = options.days;
|
|
3315
|
+
if (!days) {
|
|
3316
|
+
const defaultDays = storageService.getConfig("defaultCommitDays");
|
|
3317
|
+
days = await promptDaysToScan(defaultDays);
|
|
3318
|
+
logger.log("");
|
|
3319
|
+
}
|
|
3320
|
+
const fetchSpinner = createStepSpinner(
|
|
3321
|
+
3,
|
|
3322
|
+
TOTAL_STEPS,
|
|
3323
|
+
`Fetching work items from the last ${days} days`
|
|
3324
|
+
);
|
|
3325
|
+
fetchSpinner.start();
|
|
3326
|
+
const workItems = await adapter.fetchWorkItems({
|
|
3327
|
+
days,
|
|
3328
|
+
author: options.all ? void 0 : await adapter.getCurrentUser() || void 0
|
|
3329
|
+
});
|
|
3330
|
+
if (workItems.length === 0) {
|
|
3331
|
+
failStepSpinner(fetchSpinner, 3, TOTAL_STEPS, `No work items found in the last ${days} days`);
|
|
3332
|
+
logger.log("");
|
|
3333
|
+
logger.info("Try increasing the number of days or check your activity");
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
succeedStepSpinner(
|
|
3337
|
+
fetchSpinner,
|
|
3338
|
+
3,
|
|
3339
|
+
TOTAL_STEPS,
|
|
3340
|
+
`Found ${theme.count(workItems.length)} work item${workItems.length > 1 ? "s" : ""}`
|
|
3341
|
+
);
|
|
3342
|
+
logger.log("");
|
|
3343
|
+
logger.log(formatCommitStats(workItems));
|
|
3344
|
+
logger.log("");
|
|
3345
|
+
let sortedCommits = [...workItems];
|
|
3346
|
+
if (workItems.length > 1) {
|
|
3347
|
+
const sortOption = await promptSortOption();
|
|
3348
|
+
logger.log("");
|
|
3349
|
+
if (sortOption === "date") {
|
|
3350
|
+
sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
3351
|
+
} else if (sortOption === "size") {
|
|
3352
|
+
sortedCommits.sort((a, b) => {
|
|
3353
|
+
const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
|
|
3354
|
+
const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
|
|
3355
|
+
return sizeB - sizeA;
|
|
3356
|
+
});
|
|
3357
|
+
} else if (sortOption === "files") {
|
|
3358
|
+
sortedCommits.sort((a, b) => {
|
|
3359
|
+
const filesA = a.diffStats?.filesChanged || 0;
|
|
3360
|
+
const filesB = b.diffStats?.filesChanged || 0;
|
|
3361
|
+
return filesB - filesA;
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
const selectedShas = await promptSelectCommits(sortedCommits);
|
|
3366
|
+
if (selectedShas.length === 0) {
|
|
3367
|
+
logger.log("");
|
|
3368
|
+
logger.info(theme.secondary("No work items selected. Sync cancelled."));
|
|
3369
|
+
logger.log("");
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
|
|
3373
|
+
logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
|
|
3374
|
+
logger.log("");
|
|
3375
|
+
const existingBrags = await apiService.listBrags({ limit: 100 });
|
|
3376
|
+
logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
|
|
3377
|
+
const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
|
|
3378
|
+
logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
|
|
3379
|
+
const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
|
|
3380
|
+
const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
|
|
3381
|
+
logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
|
|
3382
|
+
if (duplicates.length > 0) {
|
|
3383
|
+
logger.log("");
|
|
3384
|
+
logger.info(
|
|
3385
|
+
colors.warning(
|
|
3386
|
+
`${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
|
|
3387
|
+
)
|
|
3388
|
+
);
|
|
3389
|
+
logger.log("");
|
|
3390
|
+
}
|
|
3391
|
+
if (newCommits.length === 0) {
|
|
3392
|
+
logger.log("");
|
|
3393
|
+
logger.info(
|
|
3394
|
+
theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
|
|
3395
|
+
);
|
|
3396
|
+
logger.log("");
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
const refineSpinner = createStepSpinner(
|
|
3400
|
+
4,
|
|
3401
|
+
TOTAL_STEPS,
|
|
3402
|
+
`Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
|
|
3403
|
+
);
|
|
3404
|
+
refineSpinner.start();
|
|
3405
|
+
const refineRequest = {
|
|
3406
|
+
brags: newCommits.map((c) => ({
|
|
3407
|
+
text: c.message,
|
|
3408
|
+
date: c.date,
|
|
3409
|
+
title: c.message.split("\n")[0]
|
|
3410
|
+
}))
|
|
3411
|
+
};
|
|
3412
|
+
const refineResponse = await apiService.refineBrags(refineRequest);
|
|
3413
|
+
let refinedBrags = refineResponse.refined_brags;
|
|
3414
|
+
succeedStepSpinner(refineSpinner, 4, TOTAL_STEPS, "Work items refined successfully");
|
|
3415
|
+
logger.log("");
|
|
3416
|
+
logger.info("Preview of refined brags:");
|
|
3417
|
+
logger.log("");
|
|
3418
|
+
logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
|
|
3419
|
+
logger.log("");
|
|
3420
|
+
const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
|
|
3421
|
+
if (acceptedBrags.length === 0) {
|
|
3422
|
+
logger.log("");
|
|
3423
|
+
logger.info(theme.secondary("No brags selected for creation. Sync cancelled."));
|
|
3424
|
+
logger.log("");
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
logger.log("");
|
|
3428
|
+
let selectedOrgId = null;
|
|
3429
|
+
const userInfo = authService.getUserInfo();
|
|
3430
|
+
if (userInfo?.id) {
|
|
3431
|
+
try {
|
|
3432
|
+
const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
|
|
3433
|
+
if (orgsResponse.items.length > 0) {
|
|
3434
|
+
selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
|
|
3435
|
+
logger.log("");
|
|
3436
|
+
}
|
|
3437
|
+
} catch {
|
|
3438
|
+
logger.debug("Failed to fetch organisations, skipping org selection");
|
|
2452
3439
|
}
|
|
2453
|
-
currentBrag = editedBrag;
|
|
2454
|
-
console.log("\n" + theme.success("\u2713 Changes saved. Review your edits:") + "\n");
|
|
2455
3440
|
}
|
|
3441
|
+
const createSpinner2 = createStepSpinner(
|
|
3442
|
+
5,
|
|
3443
|
+
TOTAL_STEPS,
|
|
3444
|
+
`Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
|
|
3445
|
+
);
|
|
3446
|
+
createSpinner2.start();
|
|
3447
|
+
const createRequest = {
|
|
3448
|
+
brags: acceptedBrags.map((refined, index) => {
|
|
3449
|
+
const originalCommit = newCommits[index];
|
|
3450
|
+
return {
|
|
3451
|
+
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
3452
|
+
title: refined.refined_title,
|
|
3453
|
+
description: refined.refined_description,
|
|
3454
|
+
tags: refined.suggested_tags,
|
|
3455
|
+
repository: repoInfo.url,
|
|
3456
|
+
date: originalCommit?.date || "",
|
|
3457
|
+
commit_url: originalCommit?.url || "",
|
|
3458
|
+
impact_score: refined.suggested_impactLevel,
|
|
3459
|
+
impact_description: refined.impact_description,
|
|
3460
|
+
attachments: originalCommit?.url ? [originalCommit.url] : [],
|
|
3461
|
+
orgId: selectedOrgId || void 0
|
|
3462
|
+
};
|
|
3463
|
+
})
|
|
3464
|
+
};
|
|
3465
|
+
const createResponse = await apiService.createBrags(createRequest);
|
|
3466
|
+
succeedStepSpinner(
|
|
3467
|
+
createSpinner2,
|
|
3468
|
+
5,
|
|
3469
|
+
TOTAL_STEPS,
|
|
3470
|
+
`Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
|
|
3471
|
+
);
|
|
3472
|
+
logger.log("");
|
|
3473
|
+
logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
|
|
3474
|
+
} catch (error) {
|
|
3475
|
+
if (error instanceof CancelPromptError) {
|
|
3476
|
+
logger.log("");
|
|
3477
|
+
logger.info(theme.secondary("Sync cancelled."));
|
|
3478
|
+
logger.log("");
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
const err = error;
|
|
3482
|
+
logger.log("");
|
|
3483
|
+
logger.log(boxen6(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
|
|
3484
|
+
process.exit(1);
|
|
2456
3485
|
}
|
|
2457
|
-
return acceptedBrags;
|
|
2458
3486
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
const isAuthenticated = await authService.isAuthenticated();
|
|
2463
|
-
if (isAuthenticated) {
|
|
2464
|
-
return true;
|
|
3487
|
+
function getErrorHint2(error) {
|
|
3488
|
+
if (error.name === "GitHubError") {
|
|
3489
|
+
return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
|
|
2465
3490
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
boxen5(
|
|
2469
|
-
theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
|
|
2470
|
-
boxStyles.warning
|
|
2471
|
-
)
|
|
2472
|
-
);
|
|
2473
|
-
logger.log("");
|
|
2474
|
-
const shouldAuth = await promptConfirm("Authenticate now?", true);
|
|
2475
|
-
if (!shouldAuth) {
|
|
2476
|
-
logger.log("");
|
|
2477
|
-
logger.info(
|
|
2478
|
-
theme.secondary("Authentication skipped. Run ") + theme.command("bragduck init") + theme.secondary(" when you're ready to authenticate.")
|
|
2479
|
-
);
|
|
2480
|
-
logger.log("");
|
|
2481
|
-
return false;
|
|
3491
|
+
if (error.name === "GitError") {
|
|
3492
|
+
return "Make sure you are in a git repository";
|
|
2482
3493
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
return
|
|
3494
|
+
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
3495
|
+
return 'Run "bragduck auth login" to login again';
|
|
3496
|
+
}
|
|
3497
|
+
if (error.name === "NetworkError") {
|
|
3498
|
+
return "Check your internet connection and try again";
|
|
2488
3499
|
}
|
|
3500
|
+
if (error.name === "ApiError") {
|
|
3501
|
+
return "The server might be experiencing issues. Try again later";
|
|
3502
|
+
}
|
|
3503
|
+
return "Try running with DEBUG=* for more information";
|
|
2489
3504
|
}
|
|
2490
3505
|
|
|
2491
|
-
// src/commands/
|
|
2492
|
-
init_auth_service();
|
|
2493
|
-
|
|
2494
|
-
// src/ui/spinners.ts
|
|
3506
|
+
// src/commands/logout.ts
|
|
2495
3507
|
init_esm_shims();
|
|
3508
|
+
init_auth_service();
|
|
3509
|
+
init_logger();
|
|
3510
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
3511
|
+
import boxen7 from "boxen";
|
|
3512
|
+
import chalk6 from "chalk";
|
|
2496
3513
|
import ora3 from "ora";
|
|
2497
|
-
function
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
3514
|
+
async function logoutCommand() {
|
|
3515
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
3516
|
+
if (!isAuthenticated) {
|
|
3517
|
+
logger.log(
|
|
3518
|
+
boxen7(
|
|
3519
|
+
`${chalk6.yellow("Not currently authenticated")}
|
|
3520
|
+
|
|
3521
|
+
${chalk6.dim("Nothing to logout from")}`,
|
|
3522
|
+
{
|
|
3523
|
+
padding: 1,
|
|
3524
|
+
margin: 1,
|
|
3525
|
+
borderStyle: "round",
|
|
3526
|
+
borderColor: "yellow"
|
|
3527
|
+
}
|
|
3528
|
+
)
|
|
3529
|
+
);
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
const userInfo = authService.getUserInfo();
|
|
3533
|
+
const userName = userInfo?.name || "Unknown User";
|
|
3534
|
+
logger.log("");
|
|
3535
|
+
const shouldLogout = await confirm2({
|
|
3536
|
+
message: `Are you sure you want to logout? (${chalk6.cyan(userName)})`,
|
|
3537
|
+
default: false
|
|
2510
3538
|
});
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
}
|
|
2515
|
-
function succeedSpinner(spinner, text) {
|
|
2516
|
-
if (text) {
|
|
2517
|
-
spinner.succeed(colors.success(text));
|
|
2518
|
-
} else {
|
|
2519
|
-
spinner.succeed();
|
|
3539
|
+
if (!shouldLogout) {
|
|
3540
|
+
logger.info("Logout cancelled");
|
|
3541
|
+
return;
|
|
2520
3542
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
spinner.
|
|
2525
|
-
|
|
2526
|
-
|
|
3543
|
+
const spinner = ora3("Logging out...").start();
|
|
3544
|
+
try {
|
|
3545
|
+
await authService.logout();
|
|
3546
|
+
spinner.succeed("Logged out successfully");
|
|
3547
|
+
logger.log("");
|
|
3548
|
+
logger.log(
|
|
3549
|
+
boxen7(
|
|
3550
|
+
`${chalk6.green.bold("\u2713 Logged out successfully")}
|
|
3551
|
+
|
|
3552
|
+
${chalk6.dim("Your credentials have been cleared")}
|
|
3553
|
+
|
|
3554
|
+
${chalk6.dim("Run")} ${chalk6.cyan("bragduck init")} ${chalk6.dim("to login again")}`,
|
|
3555
|
+
{
|
|
3556
|
+
padding: 1,
|
|
3557
|
+
margin: 1,
|
|
3558
|
+
borderStyle: "round",
|
|
3559
|
+
borderColor: "green"
|
|
3560
|
+
}
|
|
3561
|
+
)
|
|
3562
|
+
);
|
|
3563
|
+
} catch {
|
|
3564
|
+
spinner.fail("Logout failed");
|
|
3565
|
+
logger.error("Failed to logout. Please try again.");
|
|
3566
|
+
process.exit(1);
|
|
2527
3567
|
}
|
|
2528
3568
|
}
|
|
2529
|
-
function succeedStepSpinner(spinner, currentStep, totalSteps, text) {
|
|
2530
|
-
const stepIndicator = theme.step(currentStep, totalSteps);
|
|
2531
|
-
spinner.succeed(`${stepIndicator} ${colors.success(text)}`);
|
|
2532
|
-
}
|
|
2533
|
-
function failStepSpinner(spinner, currentStep, totalSteps, text) {
|
|
2534
|
-
const stepIndicator = theme.step(currentStep, totalSteps);
|
|
2535
|
-
spinner.fail(`${stepIndicator} ${colors.error(text)}`);
|
|
2536
|
-
}
|
|
2537
3569
|
|
|
2538
3570
|
// src/commands/scan.ts
|
|
3571
|
+
init_esm_shims();
|
|
3572
|
+
import boxen8 from "boxen";
|
|
3573
|
+
import chalk7 from "chalk";
|
|
3574
|
+
init_api_service();
|
|
3575
|
+
init_storage_service();
|
|
3576
|
+
init_logger();
|
|
3577
|
+
init_browser();
|
|
3578
|
+
init_auth_service();
|
|
2539
3579
|
async function scanCommand(options = {}) {
|
|
3580
|
+
logger.log("");
|
|
3581
|
+
logger.log(
|
|
3582
|
+
boxen8(
|
|
3583
|
+
chalk7.yellow.bold("\u26A0 Deprecation Notice") + `
|
|
3584
|
+
|
|
3585
|
+
The ${chalk7.cyan("scan")} command is deprecated.
|
|
3586
|
+
Please use ${chalk7.cyan("bragduck sync")} instead.
|
|
3587
|
+
|
|
3588
|
+
` + chalk7.dim("This command will be removed in v3.0.0"),
|
|
3589
|
+
{
|
|
3590
|
+
padding: 1,
|
|
3591
|
+
borderStyle: "round",
|
|
3592
|
+
borderColor: "yellow",
|
|
3593
|
+
dimBorder: true
|
|
3594
|
+
}
|
|
3595
|
+
)
|
|
3596
|
+
);
|
|
2540
3597
|
logger.log("");
|
|
2541
3598
|
const TOTAL_STEPS = 5;
|
|
2542
3599
|
try {
|
|
@@ -2555,7 +3612,7 @@ async function scanCommand(options = {}) {
|
|
|
2555
3612
|
logger.debug("FREE tier detected - blocking scan command");
|
|
2556
3613
|
logger.log("");
|
|
2557
3614
|
logger.log(
|
|
2558
|
-
|
|
3615
|
+
boxen8(
|
|
2559
3616
|
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!"),
|
|
2560
3617
|
{
|
|
2561
3618
|
...boxStyles.warning,
|
|
@@ -2758,7 +3815,7 @@ async function scanCommand(options = {}) {
|
|
|
2758
3815
|
`Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
|
|
2759
3816
|
);
|
|
2760
3817
|
logger.log("");
|
|
2761
|
-
logger.log(
|
|
3818
|
+
logger.log(boxen8(formatSuccessMessage(createResponse.created), boxStyles.success));
|
|
2762
3819
|
} catch (error) {
|
|
2763
3820
|
if (error instanceof CancelPromptError) {
|
|
2764
3821
|
logger.log("");
|
|
@@ -2768,11 +3825,11 @@ async function scanCommand(options = {}) {
|
|
|
2768
3825
|
}
|
|
2769
3826
|
const err = error;
|
|
2770
3827
|
logger.log("");
|
|
2771
|
-
logger.log(
|
|
3828
|
+
logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
|
|
2772
3829
|
process.exit(1);
|
|
2773
3830
|
}
|
|
2774
3831
|
}
|
|
2775
|
-
function
|
|
3832
|
+
function getErrorHint3(error) {
|
|
2776
3833
|
if (error.name === "GitHubError") {
|
|
2777
3834
|
return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
|
|
2778
3835
|
}
|
|
@@ -2794,7 +3851,7 @@ function getErrorHint2(error) {
|
|
|
2794
3851
|
// src/commands/list.ts
|
|
2795
3852
|
init_esm_shims();
|
|
2796
3853
|
init_api_service();
|
|
2797
|
-
import
|
|
3854
|
+
import boxen9 from "boxen";
|
|
2798
3855
|
import Table2 from "cli-table3";
|
|
2799
3856
|
init_logger();
|
|
2800
3857
|
async function listCommand(options = {}) {
|
|
@@ -2861,7 +3918,7 @@ async function listCommand(options = {}) {
|
|
|
2861
3918
|
} catch (error) {
|
|
2862
3919
|
const err = error;
|
|
2863
3920
|
logger.log("");
|
|
2864
|
-
logger.log(
|
|
3921
|
+
logger.log(boxen9(formatErrorMessage(err.message, getErrorHint4(err)), boxStyles.error));
|
|
2865
3922
|
process.exit(1);
|
|
2866
3923
|
}
|
|
2867
3924
|
}
|
|
@@ -2914,7 +3971,7 @@ function extractRepoName(url) {
|
|
|
2914
3971
|
return url;
|
|
2915
3972
|
}
|
|
2916
3973
|
}
|
|
2917
|
-
function
|
|
3974
|
+
function getErrorHint4(error) {
|
|
2918
3975
|
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
2919
3976
|
return 'Run "bragduck init" to login again';
|
|
2920
3977
|
}
|
|
@@ -2932,8 +3989,8 @@ init_esm_shims();
|
|
|
2932
3989
|
init_storage_service();
|
|
2933
3990
|
init_logger();
|
|
2934
3991
|
init_constants();
|
|
2935
|
-
import
|
|
2936
|
-
import
|
|
3992
|
+
import boxen10 from "boxen";
|
|
3993
|
+
import chalk8 from "chalk";
|
|
2937
3994
|
import Table3 from "cli-table3";
|
|
2938
3995
|
init_errors();
|
|
2939
3996
|
var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
|
|
@@ -2968,7 +4025,7 @@ async function configCommand(subcommand, key, value) {
|
|
|
2968
4025
|
const err = error;
|
|
2969
4026
|
logger.log("");
|
|
2970
4027
|
logger.log(
|
|
2971
|
-
|
|
4028
|
+
boxen10(formatErrorMessage(err.message, getConfigHint(err)), {
|
|
2972
4029
|
padding: 1,
|
|
2973
4030
|
margin: 1,
|
|
2974
4031
|
borderStyle: "round",
|
|
@@ -2984,7 +4041,7 @@ async function handleListConfig() {
|
|
|
2984
4041
|
autoVersionCheck: storageService.getConfig("autoVersionCheck")
|
|
2985
4042
|
};
|
|
2986
4043
|
const table = new Table3({
|
|
2987
|
-
head: [
|
|
4044
|
+
head: [chalk8.cyan("Key"), chalk8.cyan("Value"), chalk8.cyan("Default")],
|
|
2988
4045
|
colWidths: [25, 40, 40],
|
|
2989
4046
|
wordWrap: true,
|
|
2990
4047
|
style: {
|
|
@@ -2993,36 +4050,36 @@ async function handleListConfig() {
|
|
|
2993
4050
|
}
|
|
2994
4051
|
});
|
|
2995
4052
|
table.push([
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
4053
|
+
chalk8.white("defaultCommitDays"),
|
|
4054
|
+
chalk8.yellow(String(config2.defaultCommitDays)),
|
|
4055
|
+
chalk8.dim(String(DEFAULT_CONFIG.defaultCommitDays))
|
|
2999
4056
|
]);
|
|
3000
4057
|
table.push([
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
4058
|
+
chalk8.white("autoVersionCheck"),
|
|
4059
|
+
chalk8.yellow(String(config2.autoVersionCheck)),
|
|
4060
|
+
chalk8.dim(String(DEFAULT_CONFIG.autoVersionCheck))
|
|
3004
4061
|
]);
|
|
3005
4062
|
logger.info("Current configuration:");
|
|
3006
4063
|
logger.log("");
|
|
3007
4064
|
logger.log(table.toString());
|
|
3008
4065
|
logger.log("");
|
|
3009
|
-
logger.info(
|
|
4066
|
+
logger.info(chalk8.dim("To change a value: ") + chalk8.cyan("bragduck config set <key> <value>"));
|
|
3010
4067
|
logger.log("");
|
|
3011
4068
|
}
|
|
3012
4069
|
async function handleGetConfig(key) {
|
|
3013
4070
|
validateConfigKey(key);
|
|
3014
4071
|
const value = storageService.getConfig(key);
|
|
3015
4072
|
const defaultValue = DEFAULT_CONFIG[key];
|
|
3016
|
-
logger.info(`Configuration for ${
|
|
4073
|
+
logger.info(`Configuration for ${chalk8.cyan(key)}:`);
|
|
3017
4074
|
logger.log("");
|
|
3018
|
-
logger.log(` ${
|
|
3019
|
-
logger.log(` ${
|
|
4075
|
+
logger.log(` ${chalk8.white("Current:")} ${chalk8.yellow(String(value))}`);
|
|
4076
|
+
logger.log(` ${chalk8.white("Default:")} ${chalk8.dim(String(defaultValue))}`);
|
|
3020
4077
|
logger.log("");
|
|
3021
4078
|
if (value === defaultValue) {
|
|
3022
|
-
logger.info(
|
|
4079
|
+
logger.info(chalk8.dim("Using default value"));
|
|
3023
4080
|
} else {
|
|
3024
4081
|
logger.info(
|
|
3025
|
-
|
|
4082
|
+
chalk8.dim("Custom value set. Reset with: ") + chalk8.cyan(`bragduck config set ${key} ${defaultValue}`)
|
|
3026
4083
|
);
|
|
3027
4084
|
}
|
|
3028
4085
|
logger.log("");
|
|
@@ -3032,10 +4089,10 @@ async function handleSetConfig(key, value) {
|
|
|
3032
4089
|
const typedValue = validateAndConvertValue(key, value);
|
|
3033
4090
|
storageService.setConfig(key, typedValue);
|
|
3034
4091
|
logger.log(
|
|
3035
|
-
|
|
3036
|
-
`${
|
|
4092
|
+
boxen10(
|
|
4093
|
+
`${chalk8.green.bold("\u2713 Configuration updated")}
|
|
3037
4094
|
|
|
3038
|
-
${
|
|
4095
|
+
${chalk8.white(key)}: ${chalk8.yellow(String(typedValue))}`,
|
|
3039
4096
|
{
|
|
3040
4097
|
padding: 1,
|
|
3041
4098
|
margin: 1,
|
|
@@ -3101,6 +4158,22 @@ var packageJsonPath = join7(__dirname6, "../../package.json");
|
|
|
3101
4158
|
var packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
|
|
3102
4159
|
var program = new Command();
|
|
3103
4160
|
program.name("bragduck").description("CLI tool for managing developer achievements and brags\nAliases: bd, 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)");
|
|
4161
|
+
program.command("auth [subcommand]").description("Manage authentication (subcommands: login, status, bitbucket)").action(async (subcommand) => {
|
|
4162
|
+
try {
|
|
4163
|
+
await authCommand(subcommand);
|
|
4164
|
+
} catch (error) {
|
|
4165
|
+
console.error(error);
|
|
4166
|
+
process.exit(1);
|
|
4167
|
+
}
|
|
4168
|
+
});
|
|
4169
|
+
program.command("sync").description("Sync work items and create brags").option("-d, --days <number>", "Number of days to scan", (val) => parseInt(val, 10)).option("-a, --all", "Include all items (not just current user)").option("-s, --source <type>", "Explicit source type (github)").action(async (options) => {
|
|
4170
|
+
try {
|
|
4171
|
+
await syncCommand(options);
|
|
4172
|
+
} catch (error) {
|
|
4173
|
+
console.error(error);
|
|
4174
|
+
process.exit(1);
|
|
4175
|
+
}
|
|
4176
|
+
});
|
|
3104
4177
|
program.command("init").description("Authenticate with Bragduck").action(async () => {
|
|
3105
4178
|
try {
|
|
3106
4179
|
await initCommand();
|
|
@@ -3109,7 +4182,7 @@ program.command("init").description("Authenticate with Bragduck").action(async (
|
|
|
3109
4182
|
process.exit(1);
|
|
3110
4183
|
}
|
|
3111
4184
|
});
|
|
3112
|
-
program.command("scan").description("Scan git commits and create brags").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) => {
|
|
4185
|
+
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) => {
|
|
3113
4186
|
try {
|
|
3114
4187
|
await scanCommand(options);
|
|
3115
4188
|
} catch (error) {
|