@bragduck/cli 2.28.1 → 2.29.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/dist/bin/bragduck.js +445 -128
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -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";
|
|
@@ -57,11 +57,23 @@ var init_constants = __esm({
|
|
|
57
57
|
MIN_PORT: 8e3,
|
|
58
58
|
MAX_PORT: 9e3
|
|
59
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"
|
|
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
|
|
|
@@ -1241,15 +1259,15 @@ __export(version_exports, {
|
|
|
1241
1259
|
getCurrentVersion: () => getCurrentVersion,
|
|
1242
1260
|
version: () => version
|
|
1243
1261
|
});
|
|
1244
|
-
import { readFileSync as
|
|
1245
|
-
import { fileURLToPath as
|
|
1246
|
-
import { dirname as
|
|
1262
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1263
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1264
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
1247
1265
|
import chalk4 from "chalk";
|
|
1248
1266
|
import boxen2 from "boxen";
|
|
1249
1267
|
function getCurrentVersion() {
|
|
1250
1268
|
try {
|
|
1251
|
-
const packageJsonPath2 =
|
|
1252
|
-
const packageJson2 = JSON.parse(
|
|
1269
|
+
const packageJsonPath2 = join6(__dirname5, "../../package.json");
|
|
1270
|
+
const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
|
|
1253
1271
|
return packageJson2.version;
|
|
1254
1272
|
} catch {
|
|
1255
1273
|
logger.debug("Failed to read package.json version");
|
|
@@ -1331,7 +1349,7 @@ function formatVersion(includePrefix = true) {
|
|
|
1331
1349
|
const prefix = includePrefix ? "v" : "";
|
|
1332
1350
|
return `${prefix}${version}`;
|
|
1333
1351
|
}
|
|
1334
|
-
var
|
|
1352
|
+
var __filename5, __dirname5, version;
|
|
1335
1353
|
var init_version = __esm({
|
|
1336
1354
|
"src/utils/version.ts"() {
|
|
1337
1355
|
"use strict";
|
|
@@ -1339,22 +1357,22 @@ var init_version = __esm({
|
|
|
1339
1357
|
init_api_service();
|
|
1340
1358
|
init_storage_service();
|
|
1341
1359
|
init_logger();
|
|
1342
|
-
|
|
1343
|
-
|
|
1360
|
+
__filename5 = fileURLToPath5(import.meta.url);
|
|
1361
|
+
__dirname5 = dirname4(__filename5);
|
|
1344
1362
|
version = getCurrentVersion();
|
|
1345
1363
|
}
|
|
1346
1364
|
});
|
|
1347
1365
|
|
|
1348
1366
|
// 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
|
|
1367
|
+
import { ofetch as ofetch3 } from "ofetch";
|
|
1368
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1369
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
1370
|
+
import { URLSearchParams as URLSearchParams3 } from "url";
|
|
1371
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1354
1372
|
function getCliVersion() {
|
|
1355
1373
|
try {
|
|
1356
|
-
const packageJsonPath2 =
|
|
1357
|
-
const packageJson2 = JSON.parse(
|
|
1374
|
+
const packageJsonPath2 = join7(__dirname6, "../../package.json");
|
|
1375
|
+
const packageJson2 = JSON.parse(readFileSync5(packageJsonPath2, "utf-8"));
|
|
1358
1376
|
return packageJson2.version;
|
|
1359
1377
|
} catch {
|
|
1360
1378
|
logger.debug("Failed to read package.json version");
|
|
@@ -1366,7 +1384,7 @@ function getPlatformInfo() {
|
|
|
1366
1384
|
const arch = process.arch;
|
|
1367
1385
|
return `${platform}-${arch}`;
|
|
1368
1386
|
}
|
|
1369
|
-
var
|
|
1387
|
+
var __filename6, __dirname6, ApiService, apiService;
|
|
1370
1388
|
var init_api_service = __esm({
|
|
1371
1389
|
"src/services/api.service.ts"() {
|
|
1372
1390
|
"use strict";
|
|
@@ -1375,14 +1393,14 @@ var init_api_service = __esm({
|
|
|
1375
1393
|
init_constants();
|
|
1376
1394
|
init_errors();
|
|
1377
1395
|
init_logger();
|
|
1378
|
-
|
|
1379
|
-
|
|
1396
|
+
__filename6 = fileURLToPath6(import.meta.url);
|
|
1397
|
+
__dirname6 = dirname5(__filename6);
|
|
1380
1398
|
ApiService = class {
|
|
1381
1399
|
baseURL;
|
|
1382
1400
|
client;
|
|
1383
1401
|
constructor() {
|
|
1384
1402
|
this.baseURL = process.env.API_BASE_URL || "https://api.bragduck.com";
|
|
1385
|
-
this.client =
|
|
1403
|
+
this.client = ofetch3.create({
|
|
1386
1404
|
baseURL: this.baseURL,
|
|
1387
1405
|
// Request interceptor
|
|
1388
1406
|
onRequest: async ({ request, options }) => {
|
|
@@ -1546,7 +1564,7 @@ var init_api_service = __esm({
|
|
|
1546
1564
|
const { limit = 50, offset = 0, tags, search } = params;
|
|
1547
1565
|
logger.debug(`Listing brags: limit=${limit}, offset=${offset}`);
|
|
1548
1566
|
try {
|
|
1549
|
-
const queryParams = new
|
|
1567
|
+
const queryParams = new URLSearchParams3({
|
|
1550
1568
|
limit: limit.toString(),
|
|
1551
1569
|
offset: offset.toString()
|
|
1552
1570
|
});
|
|
@@ -1643,7 +1661,7 @@ var init_api_service = __esm({
|
|
|
1643
1661
|
*/
|
|
1644
1662
|
setBaseURL(url) {
|
|
1645
1663
|
this.baseURL = url;
|
|
1646
|
-
this.client =
|
|
1664
|
+
this.client = ofetch3.create({
|
|
1647
1665
|
baseURL: url
|
|
1648
1666
|
});
|
|
1649
1667
|
}
|
|
@@ -1661,18 +1679,267 @@ var init_api_service = __esm({
|
|
|
1661
1679
|
// src/cli.ts
|
|
1662
1680
|
init_esm_shims();
|
|
1663
1681
|
import { Command } from "commander";
|
|
1664
|
-
import { readFileSync as
|
|
1665
|
-
import { fileURLToPath as
|
|
1666
|
-
import { dirname as
|
|
1682
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1683
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
1684
|
+
import { dirname as dirname6, join as join8 } from "path";
|
|
1667
1685
|
|
|
1668
1686
|
// src/commands/auth.ts
|
|
1669
1687
|
init_esm_shims();
|
|
1670
1688
|
init_auth_service();
|
|
1671
|
-
init_storage_service();
|
|
1672
1689
|
import boxen from "boxen";
|
|
1673
1690
|
import chalk3 from "chalk";
|
|
1674
1691
|
import { input } from "@inquirer/prompts";
|
|
1675
1692
|
|
|
1693
|
+
// src/services/atlassian-auth.service.ts
|
|
1694
|
+
init_esm_shims();
|
|
1695
|
+
init_constants();
|
|
1696
|
+
init_storage_service();
|
|
1697
|
+
init_oauth_server();
|
|
1698
|
+
init_browser();
|
|
1699
|
+
init_errors();
|
|
1700
|
+
init_logger();
|
|
1701
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
1702
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1703
|
+
import { fileURLToPath as fileURLToPath4, URLSearchParams as URLSearchParams2 } from "url";
|
|
1704
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1705
|
+
import { ofetch as ofetch2 } from "ofetch";
|
|
1706
|
+
import { select } from "@inquirer/prompts";
|
|
1707
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1708
|
+
var __dirname4 = dirname3(__filename4);
|
|
1709
|
+
function getUserAgent2() {
|
|
1710
|
+
try {
|
|
1711
|
+
const packageJsonPath2 = join4(__dirname4, "../../package.json");
|
|
1712
|
+
const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
|
|
1713
|
+
const version2 = packageJson2.version;
|
|
1714
|
+
const platform = process.platform;
|
|
1715
|
+
const arch = process.arch;
|
|
1716
|
+
return `BragDuck-CLI/${version2} (${platform}-${arch})`;
|
|
1717
|
+
} catch {
|
|
1718
|
+
logger.debug("Failed to read package.json version");
|
|
1719
|
+
return "BragDuck-CLI/2.0.0";
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
var AtlassianAuthService = class {
|
|
1723
|
+
apiBaseUrl;
|
|
1724
|
+
refreshPromise = null;
|
|
1725
|
+
constructor() {
|
|
1726
|
+
this.apiBaseUrl = process.env.API_BASE_URL || "https://api.bragduck.com";
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Generate a random state string for CSRF protection
|
|
1730
|
+
*/
|
|
1731
|
+
generateState() {
|
|
1732
|
+
return randomBytes3(32).toString("hex");
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Build the Atlassian OAuth authorization URL
|
|
1736
|
+
*/
|
|
1737
|
+
buildAuthUrl(state, callbackUrl) {
|
|
1738
|
+
const params = new URLSearchParams2({
|
|
1739
|
+
audience: ATLASSIAN_OAUTH_CONFIG.AUDIENCE,
|
|
1740
|
+
client_id: ATLASSIAN_OAUTH_CONFIG.CLIENT_ID,
|
|
1741
|
+
scope: ATLASSIAN_OAUTH_CONFIG.SCOPES,
|
|
1742
|
+
redirect_uri: callbackUrl,
|
|
1743
|
+
state,
|
|
1744
|
+
response_type: "code",
|
|
1745
|
+
prompt: "consent"
|
|
1746
|
+
});
|
|
1747
|
+
return `${ATLASSIAN_OAUTH_CONFIG.AUTH_URL}?${params.toString()}`;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Exchange authorization code for tokens via BragDuck backend
|
|
1751
|
+
*/
|
|
1752
|
+
async exchangeCodeForToken(code, redirectUri) {
|
|
1753
|
+
try {
|
|
1754
|
+
logger.debug("Exchanging Atlassian authorization code for token via backend");
|
|
1755
|
+
const response = await ofetch2(
|
|
1756
|
+
`${this.apiBaseUrl}${API_ENDPOINTS.ATLASSIAN.TOKEN}`,
|
|
1757
|
+
{
|
|
1758
|
+
method: "POST",
|
|
1759
|
+
body: {
|
|
1760
|
+
code,
|
|
1761
|
+
redirect_uri: redirectUri
|
|
1762
|
+
},
|
|
1763
|
+
headers: {
|
|
1764
|
+
"Content-Type": "application/json",
|
|
1765
|
+
"User-Agent": getUserAgent2()
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
);
|
|
1769
|
+
logger.debug("Atlassian token exchange successful");
|
|
1770
|
+
return response;
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
logger.debug(`Atlassian token exchange failed: ${error.message}`);
|
|
1773
|
+
throw new AtlassianError("Failed to exchange Atlassian authorization code", {
|
|
1774
|
+
originalError: error.message
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Fetch accessible Atlassian Cloud resources (sites) using the access token
|
|
1780
|
+
*/
|
|
1781
|
+
async fetchAccessibleResources(accessToken) {
|
|
1782
|
+
try {
|
|
1783
|
+
logger.debug("Fetching accessible Atlassian resources");
|
|
1784
|
+
const resources = await ofetch2(
|
|
1785
|
+
ATLASSIAN_OAUTH_CONFIG.ACCESSIBLE_RESOURCES_URL,
|
|
1786
|
+
{
|
|
1787
|
+
headers: {
|
|
1788
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1789
|
+
Accept: "application/json"
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
);
|
|
1793
|
+
logger.debug(`Found ${resources.length} accessible resource(s)`);
|
|
1794
|
+
return resources;
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
logger.debug(`Failed to fetch accessible resources: ${error.message}`);
|
|
1797
|
+
throw new AtlassianError("Failed to fetch Atlassian Cloud sites", {
|
|
1798
|
+
originalError: error.message
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Let user pick a site if multiple accessible resources exist
|
|
1804
|
+
*/
|
|
1805
|
+
async selectSite(resources) {
|
|
1806
|
+
if (resources.length === 0) {
|
|
1807
|
+
throw new AtlassianError(
|
|
1808
|
+
"No accessible Atlassian Cloud sites found. Make sure your account has access to at least one site."
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
if (resources.length === 1) {
|
|
1812
|
+
const site = resources[0];
|
|
1813
|
+
logger.debug(`Auto-selecting single site: ${site.name}`);
|
|
1814
|
+
return site;
|
|
1815
|
+
}
|
|
1816
|
+
const selectedId = await select({
|
|
1817
|
+
message: "Select your Atlassian Cloud site:",
|
|
1818
|
+
choices: resources.map((r) => ({
|
|
1819
|
+
name: `${r.name} (${r.url})`,
|
|
1820
|
+
value: r.id
|
|
1821
|
+
}))
|
|
1822
|
+
});
|
|
1823
|
+
return resources.find((r) => r.id === selectedId);
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Full OAuth login flow:
|
|
1827
|
+
* 1. Open browser to Atlassian consent screen
|
|
1828
|
+
* 2. Wait for callback with authorization code
|
|
1829
|
+
* 3. Exchange code for tokens via BragDuck backend
|
|
1830
|
+
* 4. Fetch accessible resources and pick site
|
|
1831
|
+
* 5. Store credentials for jira, confluence, and atlassian services
|
|
1832
|
+
*/
|
|
1833
|
+
async login() {
|
|
1834
|
+
logger.debug("Starting Atlassian OAuth login flow");
|
|
1835
|
+
const state = this.generateState();
|
|
1836
|
+
const callbackUrl = await getCallbackUrl();
|
|
1837
|
+
storageService.setOAuthState({
|
|
1838
|
+
state,
|
|
1839
|
+
createdAt: Date.now()
|
|
1840
|
+
});
|
|
1841
|
+
logger.debug(`OAuth state: ${state}`);
|
|
1842
|
+
logger.debug(`Callback URL: ${callbackUrl}`);
|
|
1843
|
+
const authUrl = this.buildAuthUrl(state, callbackUrl);
|
|
1844
|
+
logger.debug(`Authorization URL: ${authUrl}`);
|
|
1845
|
+
const serverPromise = startOAuthCallbackServer(state);
|
|
1846
|
+
try {
|
|
1847
|
+
await openBrowser(authUrl);
|
|
1848
|
+
} catch {
|
|
1849
|
+
logger.warning("Could not open browser automatically");
|
|
1850
|
+
logger.info("Please open this URL in your browser:");
|
|
1851
|
+
logger.log(authUrl);
|
|
1852
|
+
}
|
|
1853
|
+
let callbackResult;
|
|
1854
|
+
try {
|
|
1855
|
+
callbackResult = await serverPromise;
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
storageService.deleteOAuthState();
|
|
1858
|
+
throw error;
|
|
1859
|
+
}
|
|
1860
|
+
storageService.deleteOAuthState();
|
|
1861
|
+
const tokenResponse = await this.exchangeCodeForToken(callbackResult.code, callbackUrl);
|
|
1862
|
+
const resources = await this.fetchAccessibleResources(tokenResponse.access_token);
|
|
1863
|
+
const site = await this.selectSite(resources);
|
|
1864
|
+
const expiresAt = tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1e3 : void 0;
|
|
1865
|
+
const credentials = {
|
|
1866
|
+
accessToken: tokenResponse.access_token,
|
|
1867
|
+
refreshToken: tokenResponse.refresh_token,
|
|
1868
|
+
expiresAt,
|
|
1869
|
+
instanceUrl: site.url,
|
|
1870
|
+
// Real Cloud URL for browse links
|
|
1871
|
+
cloudId: site.id,
|
|
1872
|
+
authMethod: "oauth"
|
|
1873
|
+
};
|
|
1874
|
+
await storageService.setServiceCredentials("jira", credentials);
|
|
1875
|
+
await storageService.setServiceCredentials("confluence", credentials);
|
|
1876
|
+
await storageService.setServiceCredentials("atlassian", credentials);
|
|
1877
|
+
logger.debug("Atlassian OAuth login successful");
|
|
1878
|
+
return { siteName: site.name, siteUrl: site.url };
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Refresh Atlassian OAuth tokens via BragDuck backend.
|
|
1882
|
+
* Uses a singleton promise to prevent concurrent refresh race conditions
|
|
1883
|
+
* (Atlassian uses rotating refresh tokens - each refresh invalidates the previous one).
|
|
1884
|
+
*/
|
|
1885
|
+
async refreshToken() {
|
|
1886
|
+
if (this.refreshPromise) {
|
|
1887
|
+
return this.refreshPromise;
|
|
1888
|
+
}
|
|
1889
|
+
this.refreshPromise = this.doRefreshToken();
|
|
1890
|
+
try {
|
|
1891
|
+
await this.refreshPromise;
|
|
1892
|
+
} finally {
|
|
1893
|
+
this.refreshPromise = null;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
async doRefreshToken() {
|
|
1897
|
+
logger.debug("Refreshing Atlassian OAuth token");
|
|
1898
|
+
const creds = await storageService.getServiceCredentials("jira");
|
|
1899
|
+
if (!creds?.refreshToken) {
|
|
1900
|
+
throw new AtlassianError("No Atlassian refresh token available", {
|
|
1901
|
+
hint: "Run: bragduck auth atlassian"
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
const response = await ofetch2(
|
|
1906
|
+
`${this.apiBaseUrl}${API_ENDPOINTS.ATLASSIAN.REFRESH}`,
|
|
1907
|
+
{
|
|
1908
|
+
method: "POST",
|
|
1909
|
+
body: {
|
|
1910
|
+
refresh_token: creds.refreshToken
|
|
1911
|
+
},
|
|
1912
|
+
headers: {
|
|
1913
|
+
"Content-Type": "application/json",
|
|
1914
|
+
"User-Agent": getUserAgent2()
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
);
|
|
1918
|
+
const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1e3 : void 0;
|
|
1919
|
+
const updatedCreds = {
|
|
1920
|
+
...creds,
|
|
1921
|
+
accessToken: response.access_token,
|
|
1922
|
+
refreshToken: response.refresh_token,
|
|
1923
|
+
expiresAt
|
|
1924
|
+
};
|
|
1925
|
+
await storageService.setServiceCredentials("jira", updatedCreds);
|
|
1926
|
+
await storageService.setServiceCredentials("confluence", updatedCreds);
|
|
1927
|
+
await storageService.setServiceCredentials("atlassian", updatedCreds);
|
|
1928
|
+
logger.debug("Atlassian token refresh successful");
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
logger.debug(`Atlassian token refresh failed: ${error.message}`);
|
|
1931
|
+
throw new AtlassianError(
|
|
1932
|
+
'Atlassian token refresh failed. Please run "bragduck auth atlassian" to re-authenticate.',
|
|
1933
|
+
{ originalError: error.message }
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
var atlassianAuthService = new AtlassianAuthService();
|
|
1939
|
+
|
|
1940
|
+
// src/commands/auth.ts
|
|
1941
|
+
init_storage_service();
|
|
1942
|
+
|
|
1676
1943
|
// src/services/github.service.ts
|
|
1677
1944
|
init_esm_shims();
|
|
1678
1945
|
init_errors();
|
|
@@ -1688,9 +1955,9 @@ import simpleGit from "simple-git";
|
|
|
1688
1955
|
init_esm_shims();
|
|
1689
1956
|
init_errors();
|
|
1690
1957
|
import { existsSync as existsSync2 } from "fs";
|
|
1691
|
-
import { join as
|
|
1958
|
+
import { join as join5 } from "path";
|
|
1692
1959
|
function validateGitRepository(path4) {
|
|
1693
|
-
const gitDir =
|
|
1960
|
+
const gitDir = join5(path4, ".git");
|
|
1694
1961
|
if (!existsSync2(gitDir)) {
|
|
1695
1962
|
throw new GitError(
|
|
1696
1963
|
"Not a git repository. Please run this command from within a git repository.",
|
|
@@ -2404,7 +2671,14 @@ async function authStatus() {
|
|
|
2404
2671
|
}
|
|
2405
2672
|
for (const service of services) {
|
|
2406
2673
|
if (service !== "bragduck") {
|
|
2407
|
-
|
|
2674
|
+
const creds = await storageService.getServiceCredentials(service);
|
|
2675
|
+
let methodLabel = "";
|
|
2676
|
+
if (creds?.authMethod === "oauth") {
|
|
2677
|
+
methodLabel = " (Cloud - OAuth)";
|
|
2678
|
+
} else if (creds?.authMethod === "basic") {
|
|
2679
|
+
methodLabel = " (Server - API Token)";
|
|
2680
|
+
}
|
|
2681
|
+
logger.info(`${colors.success("\u2713")} ${service}: Authenticated${methodLabel}`);
|
|
2408
2682
|
}
|
|
2409
2683
|
}
|
|
2410
2684
|
logger.log("");
|
|
@@ -2556,68 +2830,21 @@ User: ${user.name} (@${user.username})`,
|
|
|
2556
2830
|
}
|
|
2557
2831
|
async function authAtlassian() {
|
|
2558
2832
|
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
|
-
);
|
|
2833
|
+
logger.info("Opening browser for Atlassian Cloud authentication...");
|
|
2565
2834
|
logger.log("");
|
|
2566
2835
|
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
|
-
});
|
|
2836
|
+
const result = await atlassianAuthService.login();
|
|
2608
2837
|
logger.log("");
|
|
2609
2838
|
logger.log(
|
|
2610
2839
|
boxen(
|
|
2611
|
-
theme.success("\u2713 Successfully authenticated with Atlassian") + `
|
|
2840
|
+
theme.success("\u2713 Successfully authenticated with Atlassian Cloud") + `
|
|
2612
2841
|
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
Email: ${user.emailAddress}
|
|
2842
|
+
Site: ${result.siteName}
|
|
2843
|
+
URL: ${result.siteUrl}
|
|
2616
2844
|
|
|
2617
2845
|
Services configured:
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
\u2022 Bitbucket`,
|
|
2846
|
+
- Jira (OAuth)
|
|
2847
|
+
- Confluence (OAuth)`,
|
|
2621
2848
|
boxStyles.success
|
|
2622
2849
|
)
|
|
2623
2850
|
);
|
|
@@ -2650,7 +2877,7 @@ var CancelPromptError = class extends Error {
|
|
|
2650
2877
|
init_api_service();
|
|
2651
2878
|
init_storage_service();
|
|
2652
2879
|
init_auth_service();
|
|
2653
|
-
import { select as
|
|
2880
|
+
import { select as select4 } from "@inquirer/prompts";
|
|
2654
2881
|
import boxen6 from "boxen";
|
|
2655
2882
|
|
|
2656
2883
|
// src/utils/source-detector.ts
|
|
@@ -2659,7 +2886,7 @@ init_errors();
|
|
|
2659
2886
|
init_storage_service();
|
|
2660
2887
|
import { exec as exec3 } from "child_process";
|
|
2661
2888
|
import { promisify as promisify3 } from "util";
|
|
2662
|
-
import { select } from "@inquirer/prompts";
|
|
2889
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
2663
2890
|
var execAsync3 = promisify3(exec3);
|
|
2664
2891
|
var SourceDetector = class {
|
|
2665
2892
|
/**
|
|
@@ -2715,7 +2942,7 @@ var SourceDetector = class {
|
|
|
2715
2942
|
description: source.remoteUrl
|
|
2716
2943
|
};
|
|
2717
2944
|
});
|
|
2718
|
-
return await
|
|
2945
|
+
return await select2({
|
|
2719
2946
|
message: "Multiple sources detected. Which do you want to sync?",
|
|
2720
2947
|
choices
|
|
2721
2948
|
});
|
|
@@ -3231,7 +3458,7 @@ init_logger();
|
|
|
3231
3458
|
init_storage_service();
|
|
3232
3459
|
import { exec as exec5 } from "child_process";
|
|
3233
3460
|
import { promisify as promisify5 } from "util";
|
|
3234
|
-
import { URLSearchParams as
|
|
3461
|
+
import { URLSearchParams as URLSearchParams4 } from "url";
|
|
3235
3462
|
var execAsync5 = promisify5(exec5);
|
|
3236
3463
|
var GitLabService = class {
|
|
3237
3464
|
DEFAULT_INSTANCE = "https://gitlab.com";
|
|
@@ -3377,7 +3604,7 @@ var GitLabService = class {
|
|
|
3377
3604
|
async getMergedMRs(options = {}) {
|
|
3378
3605
|
const { projectPath } = await this.getProjectFromGit();
|
|
3379
3606
|
const encodedPath = encodeURIComponent(projectPath);
|
|
3380
|
-
const params = new
|
|
3607
|
+
const params = new URLSearchParams4({
|
|
3381
3608
|
state: "merged",
|
|
3382
3609
|
order_by: "updated_at",
|
|
3383
3610
|
sort: "desc",
|
|
@@ -3543,41 +3770,76 @@ init_esm_shims();
|
|
|
3543
3770
|
init_errors();
|
|
3544
3771
|
init_logger();
|
|
3545
3772
|
init_storage_service();
|
|
3773
|
+
init_constants();
|
|
3546
3774
|
var JiraService = class {
|
|
3547
3775
|
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
3548
3776
|
/**
|
|
3549
|
-
* Get stored Jira credentials
|
|
3777
|
+
* Get stored Jira credentials, auto-refreshing OAuth tokens if expired
|
|
3550
3778
|
*/
|
|
3551
3779
|
async getCredentials() {
|
|
3552
3780
|
const creds = await storageService.getServiceCredentials("jira");
|
|
3553
|
-
if (!creds || !creds.
|
|
3781
|
+
if (!creds || !creds.accessToken) {
|
|
3554
3782
|
throw new JiraError("Not authenticated with Jira", {
|
|
3555
3783
|
hint: "Run: bragduck auth atlassian"
|
|
3556
3784
|
});
|
|
3557
3785
|
}
|
|
3558
|
-
if (creds.
|
|
3786
|
+
if (creds.authMethod !== "oauth" && (!creds.username || !creds.instanceUrl)) {
|
|
3787
|
+
throw new JiraError("Not authenticated with Jira", {
|
|
3788
|
+
hint: "Run: bragduck auth atlassian"
|
|
3789
|
+
});
|
|
3790
|
+
}
|
|
3791
|
+
if (creds.authMethod === "oauth" && !creds.cloudId) {
|
|
3792
|
+
throw new JiraError("Missing Atlassian Cloud ID", {
|
|
3793
|
+
hint: "Run: bragduck auth atlassian"
|
|
3794
|
+
});
|
|
3795
|
+
}
|
|
3796
|
+
if (creds.authMethod === "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
3797
|
+
if (creds.refreshToken) {
|
|
3798
|
+
logger.debug("Jira OAuth token expired, refreshing...");
|
|
3799
|
+
await atlassianAuthService.refreshToken();
|
|
3800
|
+
const refreshed = await storageService.getServiceCredentials("jira");
|
|
3801
|
+
if (!refreshed?.accessToken) {
|
|
3802
|
+
throw new JiraError("Token refresh failed", {
|
|
3803
|
+
hint: "Run: bragduck auth atlassian"
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
return refreshed;
|
|
3807
|
+
}
|
|
3808
|
+
throw new JiraError("OAuth token has expired and no refresh token available", {
|
|
3809
|
+
hint: "Run: bragduck auth atlassian"
|
|
3810
|
+
});
|
|
3811
|
+
}
|
|
3812
|
+
if (creds.authMethod !== "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
3559
3813
|
throw new JiraError("API token has expired", {
|
|
3560
3814
|
hint: "Run: bragduck auth atlassian"
|
|
3561
3815
|
});
|
|
3562
3816
|
}
|
|
3563
|
-
return
|
|
3564
|
-
email: creds.username,
|
|
3565
|
-
apiToken: creds.accessToken,
|
|
3566
|
-
instanceUrl: creds.instanceUrl
|
|
3567
|
-
};
|
|
3817
|
+
return creds;
|
|
3568
3818
|
}
|
|
3569
3819
|
/**
|
|
3570
|
-
* Make authenticated request to Jira API
|
|
3820
|
+
* Make authenticated request to Jira API.
|
|
3821
|
+
* Uses OAuth Bearer + API gateway for Cloud, Basic Auth for Server/DC.
|
|
3822
|
+
* Retries once on 401 for OAuth (auto-refresh).
|
|
3571
3823
|
*/
|
|
3572
3824
|
async request(endpoint, method = "GET", body) {
|
|
3573
|
-
const
|
|
3574
|
-
const
|
|
3575
|
-
|
|
3825
|
+
const creds = await this.getCredentials();
|
|
3826
|
+
const isOAuth = creds.authMethod === "oauth";
|
|
3827
|
+
let baseUrl;
|
|
3828
|
+
let authHeader;
|
|
3829
|
+
if (isOAuth) {
|
|
3830
|
+
baseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/jira/${creds.cloudId}`;
|
|
3831
|
+
authHeader = `Bearer ${creds.accessToken}`;
|
|
3832
|
+
} else {
|
|
3833
|
+
const instanceUrl = creds.instanceUrl;
|
|
3834
|
+
baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
|
|
3835
|
+
const auth = Buffer.from(`${creds.username}:${creds.accessToken}`).toString("base64");
|
|
3836
|
+
authHeader = `Basic ${auth}`;
|
|
3837
|
+
}
|
|
3576
3838
|
logger.debug(`Jira API: ${method} ${endpoint}`);
|
|
3577
3839
|
const options = {
|
|
3578
3840
|
method,
|
|
3579
3841
|
headers: {
|
|
3580
|
-
Authorization:
|
|
3842
|
+
Authorization: authHeader,
|
|
3581
3843
|
"Content-Type": "application/json",
|
|
3582
3844
|
Accept: "application/json"
|
|
3583
3845
|
}
|
|
@@ -3585,7 +3847,17 @@ var JiraService = class {
|
|
|
3585
3847
|
if (body) {
|
|
3586
3848
|
options.body = JSON.stringify(body);
|
|
3587
3849
|
}
|
|
3588
|
-
|
|
3850
|
+
let response = await fetch(`${baseUrl}${endpoint}`, options);
|
|
3851
|
+
if (response.status === 401 && isOAuth && creds.refreshToken) {
|
|
3852
|
+
logger.debug("Jira OAuth 401, attempting token refresh and retry");
|
|
3853
|
+
await atlassianAuthService.refreshToken();
|
|
3854
|
+
const refreshedCreds = await storageService.getServiceCredentials("jira");
|
|
3855
|
+
if (refreshedCreds?.accessToken) {
|
|
3856
|
+
options.headers.Authorization = `Bearer ${refreshedCreds.accessToken}`;
|
|
3857
|
+
const newBaseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/jira/${refreshedCreds.cloudId}`;
|
|
3858
|
+
response = await fetch(`${newBaseUrl}${endpoint}`, options);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3589
3861
|
if (!response.ok) {
|
|
3590
3862
|
const statusText = response.statusText;
|
|
3591
3863
|
const status = response.status;
|
|
@@ -3991,40 +4263,75 @@ init_esm_shims();
|
|
|
3991
4263
|
init_errors();
|
|
3992
4264
|
init_logger();
|
|
3993
4265
|
init_storage_service();
|
|
4266
|
+
init_constants();
|
|
3994
4267
|
var ConfluenceService = class {
|
|
3995
4268
|
/**
|
|
3996
|
-
* Get stored Confluence credentials
|
|
4269
|
+
* Get stored Confluence credentials, auto-refreshing OAuth tokens if expired
|
|
3997
4270
|
*/
|
|
3998
4271
|
async getCredentials() {
|
|
3999
4272
|
const creds = await storageService.getServiceCredentials("confluence");
|
|
4000
|
-
if (!creds || !creds.
|
|
4273
|
+
if (!creds || !creds.accessToken) {
|
|
4001
4274
|
throw new ConfluenceError("Not authenticated with Confluence", {
|
|
4002
4275
|
hint: "Run: bragduck auth atlassian"
|
|
4003
4276
|
});
|
|
4004
4277
|
}
|
|
4005
|
-
if (creds.
|
|
4278
|
+
if (creds.authMethod !== "oauth" && (!creds.username || !creds.instanceUrl)) {
|
|
4279
|
+
throw new ConfluenceError("Not authenticated with Confluence", {
|
|
4280
|
+
hint: "Run: bragduck auth atlassian"
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
if (creds.authMethod === "oauth" && !creds.cloudId) {
|
|
4284
|
+
throw new ConfluenceError("Missing Atlassian Cloud ID", {
|
|
4285
|
+
hint: "Run: bragduck auth atlassian"
|
|
4286
|
+
});
|
|
4287
|
+
}
|
|
4288
|
+
if (creds.authMethod === "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
4289
|
+
if (creds.refreshToken) {
|
|
4290
|
+
logger.debug("Confluence OAuth token expired, refreshing...");
|
|
4291
|
+
await atlassianAuthService.refreshToken();
|
|
4292
|
+
const refreshed = await storageService.getServiceCredentials("confluence");
|
|
4293
|
+
if (!refreshed?.accessToken) {
|
|
4294
|
+
throw new ConfluenceError("Token refresh failed", {
|
|
4295
|
+
hint: "Run: bragduck auth atlassian"
|
|
4296
|
+
});
|
|
4297
|
+
}
|
|
4298
|
+
return refreshed;
|
|
4299
|
+
}
|
|
4300
|
+
throw new ConfluenceError("OAuth token has expired and no refresh token available", {
|
|
4301
|
+
hint: "Run: bragduck auth atlassian"
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
if (creds.authMethod !== "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
4006
4305
|
throw new ConfluenceError("API token has expired", {
|
|
4007
4306
|
hint: "Run: bragduck auth atlassian"
|
|
4008
4307
|
});
|
|
4009
4308
|
}
|
|
4010
|
-
return
|
|
4011
|
-
email: creds.username,
|
|
4012
|
-
apiToken: creds.accessToken,
|
|
4013
|
-
instanceUrl: creds.instanceUrl
|
|
4014
|
-
};
|
|
4309
|
+
return creds;
|
|
4015
4310
|
}
|
|
4016
4311
|
/**
|
|
4017
|
-
* Make authenticated request to Confluence API
|
|
4312
|
+
* Make authenticated request to Confluence API.
|
|
4313
|
+
* Uses OAuth Bearer + API gateway for Cloud, Basic Auth for Server/DC.
|
|
4314
|
+
* Retries once on 401 for OAuth (auto-refresh).
|
|
4018
4315
|
*/
|
|
4019
4316
|
async request(endpoint, method = "GET", body) {
|
|
4020
|
-
const
|
|
4021
|
-
const
|
|
4022
|
-
|
|
4317
|
+
const creds = await this.getCredentials();
|
|
4318
|
+
const isOAuth = creds.authMethod === "oauth";
|
|
4319
|
+
let baseUrl;
|
|
4320
|
+
let authHeader;
|
|
4321
|
+
if (isOAuth) {
|
|
4322
|
+
baseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/confluence/${creds.cloudId}`;
|
|
4323
|
+
authHeader = `Bearer ${creds.accessToken}`;
|
|
4324
|
+
} else {
|
|
4325
|
+
const instanceUrl = creds.instanceUrl;
|
|
4326
|
+
baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
|
|
4327
|
+
const auth = Buffer.from(`${creds.username}:${creds.accessToken}`).toString("base64");
|
|
4328
|
+
authHeader = `Basic ${auth}`;
|
|
4329
|
+
}
|
|
4023
4330
|
logger.debug(`Confluence API: ${method} ${endpoint}`);
|
|
4024
4331
|
const options = {
|
|
4025
4332
|
method,
|
|
4026
4333
|
headers: {
|
|
4027
|
-
Authorization:
|
|
4334
|
+
Authorization: authHeader,
|
|
4028
4335
|
"Content-Type": "application/json",
|
|
4029
4336
|
Accept: "application/json"
|
|
4030
4337
|
}
|
|
@@ -4032,7 +4339,17 @@ var ConfluenceService = class {
|
|
|
4032
4339
|
if (body) {
|
|
4033
4340
|
options.body = JSON.stringify(body);
|
|
4034
4341
|
}
|
|
4035
|
-
|
|
4342
|
+
let response = await fetch(`${baseUrl}${endpoint}`, options);
|
|
4343
|
+
if (response.status === 401 && isOAuth && creds.refreshToken) {
|
|
4344
|
+
logger.debug("Confluence OAuth 401, attempting token refresh and retry");
|
|
4345
|
+
await atlassianAuthService.refreshToken();
|
|
4346
|
+
const refreshedCreds = await storageService.getServiceCredentials("confluence");
|
|
4347
|
+
if (refreshedCreds?.accessToken) {
|
|
4348
|
+
options.headers.Authorization = `Bearer ${refreshedCreds.accessToken}`;
|
|
4349
|
+
const newBaseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/confluence/${refreshedCreds.cloudId}`;
|
|
4350
|
+
response = await fetch(`${newBaseUrl}${endpoint}`, options);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4036
4353
|
if (!response.ok) {
|
|
4037
4354
|
const statusText = response.statusText;
|
|
4038
4355
|
const status = response.status;
|
|
@@ -4571,7 +4888,7 @@ init_logger();
|
|
|
4571
4888
|
|
|
4572
4889
|
// src/ui/prompts.ts
|
|
4573
4890
|
init_esm_shims();
|
|
4574
|
-
import { checkbox, confirm, input as input2, select as
|
|
4891
|
+
import { checkbox, confirm, input as input2, select as select3, editor } from "@inquirer/prompts";
|
|
4575
4892
|
import boxen4 from "boxen";
|
|
4576
4893
|
|
|
4577
4894
|
// src/ui/formatters.ts
|
|
@@ -4786,7 +5103,7 @@ async function promptDaysToScan(defaultDays = 30) {
|
|
|
4786
5103
|
{ name: "90 days", value: "90", description: "Last 3 months" },
|
|
4787
5104
|
{ name: "Custom", value: "custom", description: "Enter custom number of days" }
|
|
4788
5105
|
];
|
|
4789
|
-
const selected = await
|
|
5106
|
+
const selected = await select3({
|
|
4790
5107
|
message: "How many days back should we scan for PRs?",
|
|
4791
5108
|
choices,
|
|
4792
5109
|
default: "30"
|
|
@@ -4820,7 +5137,7 @@ async function promptScanMode() {
|
|
|
4820
5137
|
description: "Scan git commits directly"
|
|
4821
5138
|
}
|
|
4822
5139
|
];
|
|
4823
|
-
return await
|
|
5140
|
+
return await select3({
|
|
4824
5141
|
message: "What would you like to scan?",
|
|
4825
5142
|
choices,
|
|
4826
5143
|
default: "prs"
|
|
@@ -4837,7 +5154,7 @@ async function promptSortOption() {
|
|
|
4837
5154
|
{ name: "By files (most files)", value: "files", description: "Most files changed" },
|
|
4838
5155
|
{ name: "No sorting", value: "none", description: "Keep original order" }
|
|
4839
5156
|
];
|
|
4840
|
-
return await
|
|
5157
|
+
return await select3({
|
|
4841
5158
|
message: "How would you like to sort the PRs?",
|
|
4842
5159
|
choices,
|
|
4843
5160
|
default: "date"
|
|
@@ -4851,7 +5168,7 @@ async function promptSelectOrganisation(organisations) {
|
|
|
4851
5168
|
value: org.id
|
|
4852
5169
|
}))
|
|
4853
5170
|
];
|
|
4854
|
-
const selected = await
|
|
5171
|
+
const selected = await select3({
|
|
4855
5172
|
message: "Attach brags to which company?",
|
|
4856
5173
|
choices,
|
|
4857
5174
|
default: "none"
|
|
@@ -4916,7 +5233,7 @@ ${theme.label("PR Link")} ${colors.link(prUrl)}`;
|
|
|
4916
5233
|
}
|
|
4917
5234
|
console.log(boxen4(bragDetails, boxStyles.info));
|
|
4918
5235
|
console.log("");
|
|
4919
|
-
const action = await
|
|
5236
|
+
const action = await select3({
|
|
4920
5237
|
message: `What would you like to do with this brag?`,
|
|
4921
5238
|
choices: [
|
|
4922
5239
|
{ name: "\u2713 Accept", value: "accept", description: "Add this brag as-is" },
|
|
@@ -5533,7 +5850,7 @@ async function promptSelectService() {
|
|
|
5533
5850
|
description: `Sync all ${authenticatedSyncServices.length} authenticated service${authenticatedSyncServices.length > 1 ? "s" : ""}`
|
|
5534
5851
|
});
|
|
5535
5852
|
}
|
|
5536
|
-
const selected = await
|
|
5853
|
+
const selected = await select4({
|
|
5537
5854
|
message: "Select a service to sync:",
|
|
5538
5855
|
choices: serviceChoices
|
|
5539
5856
|
});
|
|
@@ -6834,10 +7151,10 @@ function getConfigHint(error) {
|
|
|
6834
7151
|
// src/cli.ts
|
|
6835
7152
|
init_version();
|
|
6836
7153
|
init_logger();
|
|
6837
|
-
var
|
|
6838
|
-
var
|
|
6839
|
-
var packageJsonPath =
|
|
6840
|
-
var packageJson = JSON.parse(
|
|
7154
|
+
var __filename7 = fileURLToPath7(import.meta.url);
|
|
7155
|
+
var __dirname7 = dirname6(__filename7);
|
|
7156
|
+
var packageJsonPath = join8(__dirname7, "../../package.json");
|
|
7157
|
+
var packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
|
|
6841
7158
|
var program = new Command();
|
|
6842
7159
|
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)");
|
|
6843
7160
|
program.command("auth [subcommand]").description("Manage authentication (subcommands: login, status, bitbucket, gitlab)").action(async (subcommand) => {
|