@bragduck/cli 2.39.0 → 2.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bragduck.js +382 -105
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -109,6 +109,9 @@ var init_constants = __esm({
|
|
|
109
109
|
CREATE: "/v1/users/{userId}/stories",
|
|
110
110
|
DELETE: "/v1/users/{userId}/stories/{storyId}"
|
|
111
111
|
},
|
|
112
|
+
INTEGRATIONS: {
|
|
113
|
+
ACCESS_TOKEN: "/v1/integrations/{provider}/access-token"
|
|
114
|
+
},
|
|
112
115
|
VERSION: "/v1/cli/version",
|
|
113
116
|
/**
|
|
114
117
|
* @deprecated Use BRAGS.REFINE instead
|
|
@@ -193,7 +196,7 @@ var init_env_loader = __esm({
|
|
|
193
196
|
});
|
|
194
197
|
|
|
195
198
|
// src/utils/errors.ts
|
|
196
|
-
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError, AtlassianError, GoogleDocsError, SlackError;
|
|
199
|
+
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError, AtlassianError, GoogleDocsError, SlackError, IntegrationError;
|
|
197
200
|
var init_errors = __esm({
|
|
198
201
|
"src/utils/errors.ts"() {
|
|
199
202
|
"use strict";
|
|
@@ -302,6 +305,12 @@ var init_errors = __esm({
|
|
|
302
305
|
this.name = "SlackError";
|
|
303
306
|
}
|
|
304
307
|
};
|
|
308
|
+
IntegrationError = class extends BragduckError {
|
|
309
|
+
constructor(message, details) {
|
|
310
|
+
super(message, "INTEGRATION_ERROR", details);
|
|
311
|
+
this.name = "IntegrationError";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
305
314
|
}
|
|
306
315
|
});
|
|
307
316
|
|
|
@@ -1300,8 +1309,8 @@ __export(version_exports, {
|
|
|
1300
1309
|
import { readFileSync as readFileSync6 } from "fs";
|
|
1301
1310
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
1302
1311
|
import { dirname as dirname6, join as join8 } from "path";
|
|
1303
|
-
import
|
|
1304
|
-
import
|
|
1312
|
+
import chalk2 from "chalk";
|
|
1313
|
+
import boxen from "boxen";
|
|
1305
1314
|
function getCurrentVersion() {
|
|
1306
1315
|
try {
|
|
1307
1316
|
const packageJsonPath2 = join8(__dirname7, "../../package.json");
|
|
@@ -1361,16 +1370,16 @@ async function checkForUpdates(options = {}) {
|
|
|
1361
1370
|
}
|
|
1362
1371
|
}
|
|
1363
1372
|
function displayUpdateNotification(currentVersion, latestVersion, critical = false) {
|
|
1364
|
-
const urgency = critical ?
|
|
1373
|
+
const urgency = critical ? chalk2.red.bold("CRITICAL UPDATE") : chalk2.yellow.bold("Update Available");
|
|
1365
1374
|
const message = `${urgency}
|
|
1366
1375
|
|
|
1367
|
-
Current version: ${
|
|
1368
|
-
Latest version: ${
|
|
1376
|
+
Current version: ${chalk2.dim(currentVersion)}
|
|
1377
|
+
Latest version: ${chalk2.green(latestVersion)}
|
|
1369
1378
|
|
|
1370
|
-
Update with: ${
|
|
1379
|
+
Update with: ${chalk2.cyan("npm install -g @bragduck/cli@latest")}`;
|
|
1371
1380
|
console.log("");
|
|
1372
1381
|
console.log(
|
|
1373
|
-
|
|
1382
|
+
boxen(message, {
|
|
1374
1383
|
padding: 1,
|
|
1375
1384
|
margin: { top: 0, right: 1, bottom: 0, left: 1 },
|
|
1376
1385
|
borderStyle: "round",
|
|
@@ -1422,7 +1431,7 @@ function getPlatformInfo() {
|
|
|
1422
1431
|
const arch = process.arch;
|
|
1423
1432
|
return `${platform}-${arch}`;
|
|
1424
1433
|
}
|
|
1425
|
-
var __filename8, __dirname8, ApiService, apiService;
|
|
1434
|
+
var __filename8, __dirname8, REFINE_BATCH_SIZE, CREATE_BATCH_SIZE, inBatches, ApiService, apiService;
|
|
1426
1435
|
var init_api_service = __esm({
|
|
1427
1436
|
"src/services/api.service.ts"() {
|
|
1428
1437
|
"use strict";
|
|
@@ -1433,6 +1442,15 @@ var init_api_service = __esm({
|
|
|
1433
1442
|
init_logger();
|
|
1434
1443
|
__filename8 = fileURLToPath8(import.meta.url);
|
|
1435
1444
|
__dirname8 = dirname7(__filename8);
|
|
1445
|
+
REFINE_BATCH_SIZE = 20;
|
|
1446
|
+
CREATE_BATCH_SIZE = 50;
|
|
1447
|
+
inBatches = (items, size) => {
|
|
1448
|
+
const batches = [];
|
|
1449
|
+
for (let start = 0; start < items.length; start += size) {
|
|
1450
|
+
batches.push(items.slice(start, start + size));
|
|
1451
|
+
}
|
|
1452
|
+
return batches;
|
|
1453
|
+
};
|
|
1436
1454
|
ApiService = class {
|
|
1437
1455
|
baseURL;
|
|
1438
1456
|
client;
|
|
@@ -1548,10 +1566,23 @@ var init_api_service = __esm({
|
|
|
1548
1566
|
};
|
|
1549
1567
|
logger.debug(`Refining ${trimmedRequest.brags.length} brags`);
|
|
1550
1568
|
try {
|
|
1551
|
-
const response =
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1569
|
+
const response = {
|
|
1570
|
+
refined_brags: [],
|
|
1571
|
+
processing_time_ms: 0,
|
|
1572
|
+
tokens_used: 0
|
|
1573
|
+
};
|
|
1574
|
+
for (const batch of inBatches(trimmedRequest.brags, REFINE_BATCH_SIZE)) {
|
|
1575
|
+
const batchResponse = await this.makeRequest(
|
|
1576
|
+
API_ENDPOINTS.BRAGS.REFINE,
|
|
1577
|
+
{
|
|
1578
|
+
method: "POST",
|
|
1579
|
+
body: { ...trimmedRequest, brags: batch }
|
|
1580
|
+
}
|
|
1581
|
+
);
|
|
1582
|
+
response.refined_brags.push(...batchResponse.refined_brags);
|
|
1583
|
+
response.processing_time_ms += batchResponse.processing_time_ms;
|
|
1584
|
+
response.tokens_used += batchResponse.tokens_used;
|
|
1585
|
+
}
|
|
1555
1586
|
logger.debug(`Successfully refined ${response.refined_brags.length} brags`);
|
|
1556
1587
|
return response;
|
|
1557
1588
|
} catch (_error) {
|
|
@@ -1585,10 +1616,18 @@ var init_api_service = __esm({
|
|
|
1585
1616
|
logger.debug(`Creating ${request.brags.length} brags`);
|
|
1586
1617
|
logger.debug(`Request body: ${JSON.stringify(request, null, 2)}`);
|
|
1587
1618
|
try {
|
|
1588
|
-
const response =
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1619
|
+
const response = { created: 0, brags: [] };
|
|
1620
|
+
for (const batch of inBatches(request.brags, CREATE_BATCH_SIZE)) {
|
|
1621
|
+
const batchResponse = await this.makeRequest(
|
|
1622
|
+
API_ENDPOINTS.BRAGS.CREATE,
|
|
1623
|
+
{
|
|
1624
|
+
method: "POST",
|
|
1625
|
+
body: { ...request, brags: batch }
|
|
1626
|
+
}
|
|
1627
|
+
);
|
|
1628
|
+
response.created += batchResponse.created;
|
|
1629
|
+
response.brags.push(...batchResponse.brags);
|
|
1630
|
+
}
|
|
1592
1631
|
logger.debug(`Response: ${JSON.stringify(response, null, 2)}`);
|
|
1593
1632
|
logger.debug(`Successfully created ${response.created} brags`);
|
|
1594
1633
|
return response;
|
|
@@ -1729,6 +1768,18 @@ var init_api_service = __esm({
|
|
|
1729
1768
|
throw error;
|
|
1730
1769
|
}
|
|
1731
1770
|
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Get the access token of a tool connected on the BragDuck web app.
|
|
1773
|
+
* The server refreshes it; error statuses are not retried.
|
|
1774
|
+
*/
|
|
1775
|
+
async getIntegrationAccessToken(provider) {
|
|
1776
|
+
logger.debug(`Fetching web-connected ${provider} access token`);
|
|
1777
|
+
const url = API_ENDPOINTS.INTEGRATIONS.ACCESS_TOKEN.replace("{provider}", provider);
|
|
1778
|
+
return this.makeRequest(url, {
|
|
1779
|
+
method: "GET",
|
|
1780
|
+
retry: 0
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1732
1783
|
/**
|
|
1733
1784
|
* Check for CLI updates
|
|
1734
1785
|
*/
|
|
@@ -1790,8 +1841,8 @@ import { dirname as dirname8, join as join10 } from "path";
|
|
|
1790
1841
|
// src/commands/auth.ts
|
|
1791
1842
|
init_esm_shims();
|
|
1792
1843
|
init_auth_service();
|
|
1793
|
-
import
|
|
1794
|
-
import
|
|
1844
|
+
import boxen2 from "boxen";
|
|
1845
|
+
import chalk4 from "chalk";
|
|
1795
1846
|
import { input } from "@inquirer/prompts";
|
|
1796
1847
|
|
|
1797
1848
|
// src/services/atlassian-auth.service.ts
|
|
@@ -2629,20 +2680,123 @@ var GitService = class {
|
|
|
2629
2680
|
};
|
|
2630
2681
|
var gitService = new GitService();
|
|
2631
2682
|
|
|
2683
|
+
// src/services/integrations.service.ts
|
|
2684
|
+
init_esm_shims();
|
|
2685
|
+
init_api_service();
|
|
2686
|
+
init_auth_service();
|
|
2687
|
+
init_storage_service();
|
|
2688
|
+
init_errors();
|
|
2689
|
+
init_logger();
|
|
2690
|
+
var WEB_APP_HOST = "app.bragduck.com";
|
|
2691
|
+
var EXPIRY_MARGIN_MS = 60 * 1e3;
|
|
2692
|
+
var PROVIDER_LABELS = {
|
|
2693
|
+
atlassian: "Atlassian",
|
|
2694
|
+
github: "GitHub",
|
|
2695
|
+
slack: "Slack",
|
|
2696
|
+
google: "Google Docs"
|
|
2697
|
+
};
|
|
2698
|
+
function toHost(url) {
|
|
2699
|
+
return url.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
2700
|
+
}
|
|
2701
|
+
var IntegrationsService = class {
|
|
2702
|
+
cache = /* @__PURE__ */ new Map();
|
|
2703
|
+
loggedSites = /* @__PURE__ */ new Set();
|
|
2704
|
+
/**
|
|
2705
|
+
* Get the access token for a web-connected tool.
|
|
2706
|
+
* Returns null when the user is not logged in to BragDuck or the tool is not connected.
|
|
2707
|
+
*/
|
|
2708
|
+
async getConnectedAccessToken(provider) {
|
|
2709
|
+
const cached = this.cache.get(provider);
|
|
2710
|
+
if (cached && this.isFresh(cached)) {
|
|
2711
|
+
return cached;
|
|
2712
|
+
}
|
|
2713
|
+
if (!await authService.isAuthenticated()) {
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
const label = PROVIDER_LABELS[provider];
|
|
2717
|
+
try {
|
|
2718
|
+
const connection = await apiService.getIntegrationAccessToken(provider);
|
|
2719
|
+
this.cache.set(provider, connection);
|
|
2720
|
+
logger.debug(`Using ${label} connected via web (${connection.accountName})`);
|
|
2721
|
+
return connection;
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
if (!(error instanceof ApiError)) {
|
|
2724
|
+
throw error;
|
|
2725
|
+
}
|
|
2726
|
+
if (error.statusCode === 404) {
|
|
2727
|
+
logger.debug(`${label} is not connected on the web`);
|
|
2728
|
+
return null;
|
|
2729
|
+
}
|
|
2730
|
+
if (error.statusCode === 409) {
|
|
2731
|
+
throw new IntegrationError(
|
|
2732
|
+
`Your ${label} connection has expired. Reconnect ${label} at ${WEB_APP_HOST}`,
|
|
2733
|
+
{ hint: `Reconnect ${label} at https://${WEB_APP_HOST}` }
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
if (error.statusCode === 502) {
|
|
2737
|
+
throw new IntegrationError(`Could not refresh your ${label} token. Try again shortly.`, {
|
|
2738
|
+
hint: `If this keeps happening, reconnect ${label} at https://${WEB_APP_HOST}`
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
throw error;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Build Jira/Confluence OAuth credentials from the web-connected Atlassian account.
|
|
2746
|
+
* Uses the site matching the configured instance, otherwise the first site.
|
|
2747
|
+
*/
|
|
2748
|
+
async getAtlassianCredentials(service) {
|
|
2749
|
+
const connection = await this.getConnectedAccessToken("atlassian");
|
|
2750
|
+
const sites = connection?.metadata.sites ?? [];
|
|
2751
|
+
if (!connection || sites.length === 0) {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
const configuredInstance = await storageService.getConfigWithHierarchy(`${service}Instance`);
|
|
2755
|
+
const matchingSite = configuredInstance ? sites.find((s) => toHost(s.url) === toHost(configuredInstance)) : void 0;
|
|
2756
|
+
const site = matchingSite ?? sites[0];
|
|
2757
|
+
if (sites.length > 1 && !this.loggedSites.has(site.id)) {
|
|
2758
|
+
this.loggedSites.add(site.id);
|
|
2759
|
+
logger.info(`Using Atlassian site ${site.name} (${site.url}) connected via web`);
|
|
2760
|
+
}
|
|
2761
|
+
return {
|
|
2762
|
+
accessToken: connection.accessToken,
|
|
2763
|
+
cloudId: site.id,
|
|
2764
|
+
instanceUrl: site.url,
|
|
2765
|
+
authMethod: "oauth"
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
isFresh(connection) {
|
|
2769
|
+
if (!connection.expiresAt) {
|
|
2770
|
+
return true;
|
|
2771
|
+
}
|
|
2772
|
+
return new Date(connection.expiresAt).getTime() - EXPIRY_MARGIN_MS > Date.now();
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
var integrationsService = new IntegrationsService();
|
|
2776
|
+
|
|
2632
2777
|
// src/services/github.service.ts
|
|
2633
2778
|
var execAsync2 = promisify2(exec2);
|
|
2634
2779
|
var GitHubService = class {
|
|
2635
2780
|
MAX_BODY_LENGTH = 5e3;
|
|
2636
2781
|
PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
|
|
2782
|
+
// Set when gh has no login of its own and GitHub is connected on the BragDuck web app
|
|
2783
|
+
useWebConnection = false;
|
|
2637
2784
|
/**
|
|
2638
2785
|
* Execute a gh CLI command with clean environment
|
|
2639
2786
|
* Unsets GITHUB_TOKEN and GH_TOKEN to prevent .envrc tokens from interfering
|
|
2640
|
-
* with gh CLI's own authentication
|
|
2787
|
+
* with gh CLI's own authentication. When using the web connection, GH_TOKEN
|
|
2788
|
+
* is set to the server-held token instead.
|
|
2641
2789
|
*/
|
|
2642
2790
|
async execGhCommand(command, cwd) {
|
|
2643
2791
|
const env = { ...process.env };
|
|
2644
2792
|
delete env.GITHUB_TOKEN;
|
|
2645
2793
|
delete env.GH_TOKEN;
|
|
2794
|
+
if (this.useWebConnection) {
|
|
2795
|
+
const connection = await integrationsService.getConnectedAccessToken("github");
|
|
2796
|
+
if (connection) {
|
|
2797
|
+
env.GH_TOKEN = connection.accessToken;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2646
2800
|
return execAsync2(command, { env, cwd: cwd || process.cwd() });
|
|
2647
2801
|
}
|
|
2648
2802
|
/**
|
|
@@ -2668,14 +2822,30 @@ var GitHubService = class {
|
|
|
2668
2822
|
}
|
|
2669
2823
|
}
|
|
2670
2824
|
/**
|
|
2671
|
-
* Check if user is authenticated with GitHub CLI
|
|
2825
|
+
* Check if user is authenticated with GitHub CLI, falling back to
|
|
2826
|
+
* the GitHub account connected on the BragDuck web app
|
|
2672
2827
|
*/
|
|
2673
2828
|
async checkAuthentication() {
|
|
2674
2829
|
try {
|
|
2675
2830
|
await this.execGhCommand("command gh auth status");
|
|
2676
2831
|
return true;
|
|
2677
2832
|
} catch {
|
|
2678
|
-
return
|
|
2833
|
+
return this.checkWebConnection();
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Use the server-held GitHub token when GitHub is connected on the web
|
|
2838
|
+
*/
|
|
2839
|
+
async checkWebConnection() {
|
|
2840
|
+
try {
|
|
2841
|
+
const connection = await integrationsService.getConnectedAccessToken("github");
|
|
2842
|
+
this.useWebConnection = connection !== null;
|
|
2843
|
+
return this.useWebConnection;
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
if (error instanceof IntegrationError) {
|
|
2846
|
+
throw new GitHubError(error.message, error.details);
|
|
2847
|
+
}
|
|
2848
|
+
throw error;
|
|
2679
2849
|
}
|
|
2680
2850
|
}
|
|
2681
2851
|
/**
|
|
@@ -2699,6 +2869,9 @@ var GitHubService = class {
|
|
|
2699
2869
|
return { installed: false, authenticated: false };
|
|
2700
2870
|
}
|
|
2701
2871
|
const authenticated = await this.checkAuthentication();
|
|
2872
|
+
if (this.useWebConnection) {
|
|
2873
|
+
return { installed, authenticated, connectedViaWeb: true };
|
|
2874
|
+
}
|
|
2702
2875
|
return { installed, authenticated };
|
|
2703
2876
|
}
|
|
2704
2877
|
/**
|
|
@@ -2873,33 +3046,33 @@ init_logger();
|
|
|
2873
3046
|
|
|
2874
3047
|
// src/ui/theme.ts
|
|
2875
3048
|
init_esm_shims();
|
|
2876
|
-
import
|
|
3049
|
+
import chalk3 from "chalk";
|
|
2877
3050
|
var colors = {
|
|
2878
3051
|
// Primary colors for main actions and interactive elements
|
|
2879
|
-
primary:
|
|
3052
|
+
primary: chalk3.cyan,
|
|
2880
3053
|
// Success states
|
|
2881
|
-
success:
|
|
2882
|
-
successBold:
|
|
3054
|
+
success: chalk3.green,
|
|
3055
|
+
successBold: chalk3.green.bold,
|
|
2883
3056
|
// Warning states
|
|
2884
|
-
warning:
|
|
2885
|
-
warningBold:
|
|
3057
|
+
warning: chalk3.yellow,
|
|
3058
|
+
warningBold: chalk3.yellow.bold,
|
|
2886
3059
|
// Error states
|
|
2887
|
-
error:
|
|
2888
|
-
errorBold:
|
|
3060
|
+
error: chalk3.red,
|
|
3061
|
+
errorBold: chalk3.red.bold,
|
|
2889
3062
|
// Info and metadata
|
|
2890
|
-
info:
|
|
2891
|
-
infoDim:
|
|
3063
|
+
info: chalk3.gray,
|
|
3064
|
+
infoDim: chalk3.dim,
|
|
2892
3065
|
// Highlighted/important data
|
|
2893
|
-
highlight:
|
|
2894
|
-
highlightCyan:
|
|
3066
|
+
highlight: chalk3.yellow.bold,
|
|
3067
|
+
highlightCyan: chalk3.cyan.bold,
|
|
2895
3068
|
// Text emphasis
|
|
2896
|
-
bold:
|
|
2897
|
-
dim:
|
|
3069
|
+
bold: chalk3.bold,
|
|
3070
|
+
dim: chalk3.dim,
|
|
2898
3071
|
// Special purpose
|
|
2899
|
-
white:
|
|
2900
|
-
gray:
|
|
3072
|
+
white: chalk3.white,
|
|
3073
|
+
gray: chalk3.gray,
|
|
2901
3074
|
// Links and URLs
|
|
2902
|
-
link:
|
|
3075
|
+
link: chalk3.blue.underline
|
|
2903
3076
|
};
|
|
2904
3077
|
var theme = {
|
|
2905
3078
|
/**
|
|
@@ -3068,13 +3241,13 @@ async function authLogin() {
|
|
|
3068
3241
|
const userInfo = authService.getUserInfo();
|
|
3069
3242
|
if (userInfo) {
|
|
3070
3243
|
logger.log(
|
|
3071
|
-
|
|
3072
|
-
`${
|
|
3244
|
+
boxen2(
|
|
3245
|
+
`${chalk4.yellow("Already authenticated!")}
|
|
3073
3246
|
|
|
3074
|
-
${
|
|
3075
|
-
${
|
|
3247
|
+
${chalk4.gray("User:")} ${userInfo.name}
|
|
3248
|
+
${chalk4.gray("Email:")} ${userInfo.email}
|
|
3076
3249
|
|
|
3077
|
-
${
|
|
3250
|
+
${chalk4.dim("Run")} ${chalk4.cyan("bragduck logout")} ${chalk4.dim("to sign out")}`,
|
|
3078
3251
|
{
|
|
3079
3252
|
padding: 1,
|
|
3080
3253
|
margin: 1,
|
|
@@ -3091,13 +3264,13 @@ ${chalk3.dim("Run")} ${chalk3.cyan("bragduck logout")} ${chalk3.dim("to sign out
|
|
|
3091
3264
|
const userInfo = await authService.login();
|
|
3092
3265
|
logger.log("");
|
|
3093
3266
|
logger.log(
|
|
3094
|
-
|
|
3095
|
-
`${
|
|
3267
|
+
boxen2(
|
|
3268
|
+
`${chalk4.green.bold("\u2713 Successfully authenticated!")}
|
|
3096
3269
|
|
|
3097
|
-
${
|
|
3098
|
-
${
|
|
3270
|
+
${chalk4.gray("Welcome,")} ${chalk4.cyan(userInfo.name)}
|
|
3271
|
+
${chalk4.gray("Email:")} ${userInfo.email}
|
|
3099
3272
|
|
|
3100
|
-
${
|
|
3273
|
+
${chalk4.dim("Use")} ${chalk4.cyan("bragduck sync")} ${chalk4.dim("to create brags")}`,
|
|
3101
3274
|
{
|
|
3102
3275
|
padding: 1,
|
|
3103
3276
|
margin: 1,
|
|
@@ -3111,7 +3284,7 @@ ${chalk3.dim("Use")} ${chalk3.cyan("bragduck sync")} ${chalk3.dim("to create bra
|
|
|
3111
3284
|
const err = error;
|
|
3112
3285
|
logger.log("");
|
|
3113
3286
|
logger.log(
|
|
3114
|
-
|
|
3287
|
+
boxen2(`${chalk4.red.bold("\u2717 Authentication Failed")}
|
|
3115
3288
|
|
|
3116
3289
|
${err.message}`, {
|
|
3117
3290
|
padding: 1,
|
|
@@ -3123,6 +3296,12 @@ ${err.message}`, {
|
|
|
3123
3296
|
process.exit(1);
|
|
3124
3297
|
}
|
|
3125
3298
|
}
|
|
3299
|
+
async function getWebConnection(provider) {
|
|
3300
|
+
return integrationsService.getConnectedAccessToken(provider).catch((error) => {
|
|
3301
|
+
logger.debug(`Could not check web-connected ${provider}: ${error}`);
|
|
3302
|
+
return null;
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3126
3305
|
async function authStatus() {
|
|
3127
3306
|
logger.log("");
|
|
3128
3307
|
logger.info("Authentication Status:");
|
|
@@ -3138,9 +3317,14 @@ async function authStatus() {
|
|
|
3138
3317
|
logger.log("");
|
|
3139
3318
|
logger.info("Git Sources:");
|
|
3140
3319
|
logger.log("");
|
|
3141
|
-
const ghStatus = await githubService.getAuthStatus()
|
|
3320
|
+
const ghStatus = await githubService.getAuthStatus().catch((error) => {
|
|
3321
|
+
logger.debug(`Could not check GitHub auth status: ${error}`);
|
|
3322
|
+
return { installed: true, authenticated: false, connectedViaWeb: false };
|
|
3323
|
+
});
|
|
3142
3324
|
if (ghStatus.installed) {
|
|
3143
|
-
if (ghStatus.
|
|
3325
|
+
if (ghStatus.connectedViaWeb) {
|
|
3326
|
+
logger.info(`${colors.success("\u2713")} GitHub: connected via web`);
|
|
3327
|
+
} else if (ghStatus.authenticated) {
|
|
3144
3328
|
logger.info(`${colors.success("\u2713")} GitHub: Authenticated`);
|
|
3145
3329
|
} else {
|
|
3146
3330
|
logger.info(`${colors.error("\u2717")} GitHub: Not authenticated`);
|
|
@@ -3185,8 +3369,15 @@ async function authStatus() {
|
|
|
3185
3369
|
if (jiraAuth) logger.info(theme.secondary(" \xB7 Jira"));
|
|
3186
3370
|
if (confluenceAuth) logger.info(theme.secondary(" \xB7 Confluence"));
|
|
3187
3371
|
} else {
|
|
3188
|
-
|
|
3189
|
-
|
|
3372
|
+
const webAtlassian = await getWebConnection("atlassian");
|
|
3373
|
+
if (webAtlassian) {
|
|
3374
|
+
logger.info(
|
|
3375
|
+
`${colors.success("\u2713")} Jira/Confluence: connected via web (${webAtlassian.accountName})`
|
|
3376
|
+
);
|
|
3377
|
+
} else {
|
|
3378
|
+
logger.info(`${colors.error("\u2717")} Atlassian (Jira + Confluence): Not connected`);
|
|
3379
|
+
logger.info(theme.secondary(" Run: bragduck auth atlassian"));
|
|
3380
|
+
}
|
|
3190
3381
|
}
|
|
3191
3382
|
logger.log("");
|
|
3192
3383
|
logger.info("Document Sources:");
|
|
@@ -3195,8 +3386,15 @@ async function authStatus() {
|
|
|
3195
3386
|
if (googleDocsAuth) {
|
|
3196
3387
|
logger.info(`${colors.success("\u2713")} Google Docs (OAuth)`);
|
|
3197
3388
|
} else {
|
|
3198
|
-
|
|
3199
|
-
|
|
3389
|
+
const webGoogle = await getWebConnection("google");
|
|
3390
|
+
if (webGoogle) {
|
|
3391
|
+
logger.info(
|
|
3392
|
+
`${colors.success("\u2713")} Google Docs: connected via web (${webGoogle.accountName})`
|
|
3393
|
+
);
|
|
3394
|
+
} else {
|
|
3395
|
+
logger.info(`${colors.error("\u2717")} Google Docs: Not connected`);
|
|
3396
|
+
logger.info(theme.secondary(" Run: bragduck auth google"));
|
|
3397
|
+
}
|
|
3200
3398
|
}
|
|
3201
3399
|
const slackAuth = await storageService.isServiceAuthenticated("slack");
|
|
3202
3400
|
if (slackAuth) {
|
|
@@ -3204,15 +3402,20 @@ async function authStatus() {
|
|
|
3204
3402
|
const userLabel = creds?.username ? ` (user: ${creds.username})` : "";
|
|
3205
3403
|
logger.info(`${colors.success("\u2713")} Slack: Connected${userLabel}`);
|
|
3206
3404
|
} else {
|
|
3207
|
-
|
|
3208
|
-
|
|
3405
|
+
const webSlack = await getWebConnection("slack");
|
|
3406
|
+
if (webSlack) {
|
|
3407
|
+
logger.info(`${colors.success("\u2713")} Slack: connected via web (${webSlack.accountName})`);
|
|
3408
|
+
} else {
|
|
3409
|
+
logger.info(`${colors.error("\u2717")} Slack: Not connected`);
|
|
3410
|
+
logger.info(theme.secondary(" Run: bragduck auth slack"));
|
|
3411
|
+
}
|
|
3209
3412
|
}
|
|
3210
3413
|
logger.log("");
|
|
3211
3414
|
}
|
|
3212
3415
|
async function authBitbucket() {
|
|
3213
3416
|
logger.log("");
|
|
3214
3417
|
logger.log(
|
|
3215
|
-
|
|
3418
|
+
boxen2(
|
|
3216
3419
|
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)"),
|
|
3217
3420
|
boxStyles.info
|
|
3218
3421
|
)
|
|
@@ -3243,7 +3446,7 @@ async function authBitbucket() {
|
|
|
3243
3446
|
if (!response.ok) {
|
|
3244
3447
|
logger.log("");
|
|
3245
3448
|
logger.log(
|
|
3246
|
-
|
|
3449
|
+
boxen2(
|
|
3247
3450
|
theme.error("\u2717 Authentication Failed") + "\n\nInvalid email or API token",
|
|
3248
3451
|
boxStyles.error
|
|
3249
3452
|
)
|
|
@@ -3264,7 +3467,7 @@ async function authBitbucket() {
|
|
|
3264
3467
|
await storageService.setServiceCredentials("bitbucket", credentials);
|
|
3265
3468
|
logger.log("");
|
|
3266
3469
|
logger.log(
|
|
3267
|
-
|
|
3470
|
+
boxen2(
|
|
3268
3471
|
theme.success("\u2713 Successfully authenticated with Bitbucket") + `
|
|
3269
3472
|
|
|
3270
3473
|
Email: ${email}
|
|
@@ -3278,7 +3481,7 @@ User: ${user.display_name}
|
|
|
3278
3481
|
const err = error;
|
|
3279
3482
|
logger.log("");
|
|
3280
3483
|
logger.log(
|
|
3281
|
-
|
|
3484
|
+
boxen2(
|
|
3282
3485
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3283
3486
|
boxStyles.error
|
|
3284
3487
|
)
|
|
@@ -3290,7 +3493,7 @@ User: ${user.display_name}
|
|
|
3290
3493
|
async function authGitLab() {
|
|
3291
3494
|
logger.log("");
|
|
3292
3495
|
logger.log(
|
|
3293
|
-
|
|
3496
|
+
boxen2(
|
|
3294
3497
|
theme.info("GitLab Personal Access Token Authentication") + "\n\nCreate a Personal Access Token at:\n" + colors.highlight("https://gitlab.com/-/profile/personal_access_tokens") + "\n\nRequired scopes:\n \u2022 api (full API access)\n \u2022 read_api (read API)\n \u2022 read_user (read user info)\n\n" + theme.warning("For self-hosted GitLab, check your instance docs"),
|
|
3295
3498
|
boxStyles.info
|
|
3296
3499
|
)
|
|
@@ -3312,7 +3515,7 @@ async function authGitLab() {
|
|
|
3312
3515
|
if (!response.ok) {
|
|
3313
3516
|
logger.log("");
|
|
3314
3517
|
logger.log(
|
|
3315
|
-
|
|
3518
|
+
boxen2(
|
|
3316
3519
|
theme.error("\u2717 Authentication Failed") + "\n\nInvalid instance URL or access token",
|
|
3317
3520
|
boxStyles.error
|
|
3318
3521
|
)
|
|
@@ -3328,7 +3531,7 @@ async function authGitLab() {
|
|
|
3328
3531
|
await storageService.setServiceCredentials("gitlab", credentials);
|
|
3329
3532
|
logger.log("");
|
|
3330
3533
|
logger.log(
|
|
3331
|
-
|
|
3534
|
+
boxen2(
|
|
3332
3535
|
theme.success("\u2713 Successfully authenticated with GitLab") + `
|
|
3333
3536
|
|
|
3334
3537
|
Instance: ${instanceUrl}
|
|
@@ -3341,7 +3544,7 @@ User: ${user.name} (@${user.username})`,
|
|
|
3341
3544
|
const err = error;
|
|
3342
3545
|
logger.log("");
|
|
3343
3546
|
logger.log(
|
|
3344
|
-
|
|
3547
|
+
boxen2(
|
|
3345
3548
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3346
3549
|
boxStyles.error
|
|
3347
3550
|
)
|
|
@@ -3358,7 +3561,7 @@ async function authAtlassian() {
|
|
|
3358
3561
|
const result = await atlassianAuthService.login();
|
|
3359
3562
|
logger.log("");
|
|
3360
3563
|
logger.log(
|
|
3361
|
-
|
|
3564
|
+
boxen2(
|
|
3362
3565
|
theme.success("\u2713 Successfully authenticated with Atlassian Cloud") + `
|
|
3363
3566
|
|
|
3364
3567
|
Site: ${result.siteName}
|
|
@@ -3375,7 +3578,7 @@ Services configured:
|
|
|
3375
3578
|
const err = error;
|
|
3376
3579
|
logger.log("");
|
|
3377
3580
|
logger.log(
|
|
3378
|
-
|
|
3581
|
+
boxen2(
|
|
3379
3582
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3380
3583
|
boxStyles.error
|
|
3381
3584
|
)
|
|
@@ -3392,7 +3595,7 @@ async function authGoogle() {
|
|
|
3392
3595
|
const result = await googleAuthService.login();
|
|
3393
3596
|
logger.log("");
|
|
3394
3597
|
logger.log(
|
|
3395
|
-
|
|
3598
|
+
boxen2(
|
|
3396
3599
|
theme.success("\u2713 Successfully authenticated with Google") + `
|
|
3397
3600
|
|
|
3398
3601
|
Account: ${result.email}
|
|
@@ -3407,7 +3610,7 @@ Services configured:
|
|
|
3407
3610
|
const err = error;
|
|
3408
3611
|
logger.log("");
|
|
3409
3612
|
logger.log(
|
|
3410
|
-
|
|
3613
|
+
boxen2(
|
|
3411
3614
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3412
3615
|
boxStyles.error
|
|
3413
3616
|
)
|
|
@@ -3424,7 +3627,7 @@ async function authSlack() {
|
|
|
3424
3627
|
const result = await slackAuthService.login();
|
|
3425
3628
|
logger.log("");
|
|
3426
3629
|
logger.log(
|
|
3427
|
-
|
|
3630
|
+
boxen2(
|
|
3428
3631
|
theme.success("\u2713 Successfully authenticated with Slack") + `
|
|
3429
3632
|
|
|
3430
3633
|
Workspace: ${result.teamName}
|
|
@@ -3439,7 +3642,7 @@ Services configured:
|
|
|
3439
3642
|
const err = error;
|
|
3440
3643
|
logger.log("");
|
|
3441
3644
|
logger.log(
|
|
3442
|
-
|
|
3645
|
+
boxen2(
|
|
3443
3646
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3444
3647
|
boxStyles.error
|
|
3445
3648
|
)
|
|
@@ -3622,8 +3825,8 @@ var SourceDetector = class {
|
|
|
3622
3825
|
async checkAuthentication(type) {
|
|
3623
3826
|
try {
|
|
3624
3827
|
if (type === "github") {
|
|
3625
|
-
|
|
3626
|
-
return
|
|
3828
|
+
const { authenticated } = await githubService.getAuthStatus();
|
|
3829
|
+
return authenticated;
|
|
3627
3830
|
} else if (type === "bitbucket" || type === "atlassian") {
|
|
3628
3831
|
return await storageService.isServiceAuthenticated("bitbucket");
|
|
3629
3832
|
} else if (type === "gitlab") {
|
|
@@ -4372,6 +4575,10 @@ var JiraService = class {
|
|
|
4372
4575
|
async getCredentials() {
|
|
4373
4576
|
const creds = await storageService.getServiceCredentials("jira");
|
|
4374
4577
|
if (!creds || !creds.accessToken) {
|
|
4578
|
+
const webCreds = await integrationsService.getAtlassianCredentials("jira");
|
|
4579
|
+
if (webCreds) {
|
|
4580
|
+
return webCreds;
|
|
4581
|
+
}
|
|
4375
4582
|
throw new JiraError("Not authenticated with Jira", {
|
|
4376
4583
|
hint: "Run: bragduck auth atlassian"
|
|
4377
4584
|
});
|
|
@@ -4498,7 +4705,7 @@ var JiraService = class {
|
|
|
4498
4705
|
try {
|
|
4499
4706
|
await this.request("/rest/api/3/myself");
|
|
4500
4707
|
} catch (error) {
|
|
4501
|
-
if (error instanceof JiraError) {
|
|
4708
|
+
if (error instanceof JiraError || error instanceof IntegrationError) {
|
|
4502
4709
|
throw error;
|
|
4503
4710
|
}
|
|
4504
4711
|
throw new JiraError("Could not access Jira instance via API", {
|
|
@@ -4632,7 +4839,8 @@ var JiraService = class {
|
|
|
4632
4839
|
"reporter",
|
|
4633
4840
|
"assignee",
|
|
4634
4841
|
"status",
|
|
4635
|
-
"issuetype"
|
|
4842
|
+
"issuetype",
|
|
4843
|
+
"project"
|
|
4636
4844
|
];
|
|
4637
4845
|
const allIssues = [];
|
|
4638
4846
|
const seenIssueKeys = /* @__PURE__ */ new Set();
|
|
@@ -4809,7 +5017,8 @@ ${truncatedDesc}`;
|
|
|
4809
5017
|
externalId: issue.key,
|
|
4810
5018
|
externalType: contribution?.type || "issue",
|
|
4811
5019
|
externalSource: "jira",
|
|
4812
|
-
externalUrl: url
|
|
5020
|
+
externalUrl: url,
|
|
5021
|
+
sourceName: issue.fields.project?.name || issue.fields.project?.key
|
|
4813
5022
|
};
|
|
4814
5023
|
}
|
|
4815
5024
|
};
|
|
@@ -4871,6 +5080,10 @@ var ConfluenceService = class {
|
|
|
4871
5080
|
async getCredentials() {
|
|
4872
5081
|
const creds = await storageService.getServiceCredentials("confluence");
|
|
4873
5082
|
if (!creds || !creds.accessToken) {
|
|
5083
|
+
const webCreds = await integrationsService.getAtlassianCredentials("confluence");
|
|
5084
|
+
if (webCreds) {
|
|
5085
|
+
return webCreds;
|
|
5086
|
+
}
|
|
4874
5087
|
throw new ConfluenceError("Not authenticated with Confluence", {
|
|
4875
5088
|
hint: "Run: bragduck auth atlassian"
|
|
4876
5089
|
});
|
|
@@ -4991,7 +5204,7 @@ var ConfluenceService = class {
|
|
|
4991
5204
|
try {
|
|
4992
5205
|
await this.request("/wiki/rest/api/content?type=page&limit=1");
|
|
4993
5206
|
} catch (error) {
|
|
4994
|
-
if (error instanceof ConfluenceError) {
|
|
5207
|
+
if (error instanceof ConfluenceError || error instanceof IntegrationError) {
|
|
4995
5208
|
throw error;
|
|
4996
5209
|
}
|
|
4997
5210
|
throw new ConfluenceError("Could not access Confluence instance via API", {
|
|
@@ -5115,7 +5328,7 @@ var ConfluenceService = class {
|
|
|
5115
5328
|
cql,
|
|
5116
5329
|
start: start.toString(),
|
|
5117
5330
|
limit: limit.toString(),
|
|
5118
|
-
expand: "version,history,children.comment,body.storage"
|
|
5331
|
+
expand: "version,history,children.comment,body.storage,space"
|
|
5119
5332
|
};
|
|
5120
5333
|
const queryString = Object.entries(params).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
5121
5334
|
const endpoint = `/wiki/rest/api/content/search?${queryString}`;
|
|
@@ -5259,7 +5472,8 @@ ${contribution.details}
|
|
|
5259
5472
|
externalId: page.id,
|
|
5260
5473
|
externalType: contribution?.type || "page",
|
|
5261
5474
|
externalSource: "confluence",
|
|
5262
|
-
externalUrl: url
|
|
5475
|
+
externalUrl: url,
|
|
5476
|
+
sourceName: page.space?.name || page.space?.key
|
|
5263
5477
|
};
|
|
5264
5478
|
}
|
|
5265
5479
|
};
|
|
@@ -5320,6 +5534,10 @@ var GoogleDocsService = class {
|
|
|
5320
5534
|
async getCredentials() {
|
|
5321
5535
|
const creds = await storageService.getServiceCredentials("google-docs");
|
|
5322
5536
|
if (!creds || !creds.accessToken) {
|
|
5537
|
+
const connection = await integrationsService.getConnectedAccessToken("google");
|
|
5538
|
+
if (connection) {
|
|
5539
|
+
return { accessToken: connection.accessToken };
|
|
5540
|
+
}
|
|
5323
5541
|
throw new GoogleDocsError("Not authenticated with Google Docs", {
|
|
5324
5542
|
hint: "Run: bragduck auth google"
|
|
5325
5543
|
});
|
|
@@ -5418,7 +5636,7 @@ var GoogleDocsService = class {
|
|
|
5418
5636
|
"https://www.googleapis.com/drive/v3/about?fields=user"
|
|
5419
5637
|
);
|
|
5420
5638
|
} catch (error) {
|
|
5421
|
-
if (error instanceof GoogleDocsError) {
|
|
5639
|
+
if (error instanceof GoogleDocsError || error instanceof IntegrationError) {
|
|
5422
5640
|
throw error;
|
|
5423
5641
|
}
|
|
5424
5642
|
throw new GoogleDocsError("Could not access Google Drive API", {
|
|
@@ -5575,6 +5793,8 @@ init_logger();
|
|
|
5575
5793
|
init_storage_service();
|
|
5576
5794
|
import { URLSearchParams as URLSearchParams8 } from "url";
|
|
5577
5795
|
var SlackService = class {
|
|
5796
|
+
// Slack user ID of the web-connected account, looked up once per process
|
|
5797
|
+
webUserId = null;
|
|
5578
5798
|
/**
|
|
5579
5799
|
* Get stored Slack credentials.
|
|
5580
5800
|
* Slack user tokens (xoxp-) don't expire by default — no expiry check needed.
|
|
@@ -5582,12 +5802,39 @@ var SlackService = class {
|
|
|
5582
5802
|
async getCredentials() {
|
|
5583
5803
|
const creds = await storageService.getServiceCredentials("slack");
|
|
5584
5804
|
if (!creds || !creds.accessToken) {
|
|
5805
|
+
const webCreds = await this.getWebCredentials();
|
|
5806
|
+
if (webCreds) {
|
|
5807
|
+
return webCreds;
|
|
5808
|
+
}
|
|
5585
5809
|
throw new SlackError("Not authenticated with Slack", {
|
|
5586
5810
|
hint: "Run: bragduck auth slack"
|
|
5587
5811
|
});
|
|
5588
5812
|
}
|
|
5589
5813
|
return creds;
|
|
5590
5814
|
}
|
|
5815
|
+
/**
|
|
5816
|
+
* Build credentials from the Slack account connected on the BragDuck web app.
|
|
5817
|
+
* The server does not return the Slack user ID, so it is looked up via auth.test.
|
|
5818
|
+
*/
|
|
5819
|
+
async getWebCredentials() {
|
|
5820
|
+
const connection = await integrationsService.getConnectedAccessToken("slack");
|
|
5821
|
+
if (!connection) {
|
|
5822
|
+
return null;
|
|
5823
|
+
}
|
|
5824
|
+
if (!this.webUserId) {
|
|
5825
|
+
const data = await this.authTest(connection.accessToken);
|
|
5826
|
+
if (!data.ok) {
|
|
5827
|
+
throw new SlackError(`Slack credentials invalid: ${data.error || "unknown error"}`, {
|
|
5828
|
+
hint: "Reconnect Slack at https://app.bragduck.com"
|
|
5829
|
+
});
|
|
5830
|
+
}
|
|
5831
|
+
this.webUserId = data.user_id;
|
|
5832
|
+
}
|
|
5833
|
+
return {
|
|
5834
|
+
accessToken: connection.accessToken,
|
|
5835
|
+
username: this.webUserId
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5591
5838
|
/**
|
|
5592
5839
|
* Make authenticated GET request to Slack Web API
|
|
5593
5840
|
*/
|
|
@@ -5632,18 +5879,24 @@ var SlackService = class {
|
|
|
5632
5879
|
return data;
|
|
5633
5880
|
}
|
|
5634
5881
|
/**
|
|
5635
|
-
*
|
|
5882
|
+
* Call Slack auth.test with the given token
|
|
5636
5883
|
*/
|
|
5637
|
-
async
|
|
5638
|
-
const creds = await this.getCredentials();
|
|
5884
|
+
async authTest(accessToken) {
|
|
5639
5885
|
const response = await fetch("https://slack.com/api/auth.test", {
|
|
5640
5886
|
method: "POST",
|
|
5641
5887
|
headers: {
|
|
5642
|
-
Authorization: `Bearer ${
|
|
5888
|
+
Authorization: `Bearer ${accessToken}`,
|
|
5643
5889
|
"Content-Type": "application/json"
|
|
5644
5890
|
}
|
|
5645
5891
|
});
|
|
5646
|
-
|
|
5892
|
+
return await response.json();
|
|
5893
|
+
}
|
|
5894
|
+
/**
|
|
5895
|
+
* Validate Slack credentials via auth.test
|
|
5896
|
+
*/
|
|
5897
|
+
async validateCredentials() {
|
|
5898
|
+
const creds = await this.getCredentials();
|
|
5899
|
+
const data = await this.authTest(creds.accessToken);
|
|
5647
5900
|
if (!data.ok) {
|
|
5648
5901
|
throw new SlackError(`Slack credentials invalid: ${data.error || "unknown error"}`, {
|
|
5649
5902
|
hint: "Run: bragduck auth slack"
|
|
@@ -5656,14 +5909,7 @@ var SlackService = class {
|
|
|
5656
5909
|
async getCurrentUser() {
|
|
5657
5910
|
try {
|
|
5658
5911
|
const creds = await this.getCredentials();
|
|
5659
|
-
const
|
|
5660
|
-
method: "POST",
|
|
5661
|
-
headers: {
|
|
5662
|
-
Authorization: `Bearer ${creds.accessToken}`,
|
|
5663
|
-
"Content-Type": "application/json"
|
|
5664
|
-
}
|
|
5665
|
-
});
|
|
5666
|
-
const data = await response.json();
|
|
5912
|
+
const data = await this.authTest(creds.accessToken);
|
|
5667
5913
|
return data.ok ? data.user : null;
|
|
5668
5914
|
} catch {
|
|
5669
5915
|
return null;
|
|
@@ -5736,7 +5982,9 @@ var SlackService = class {
|
|
|
5736
5982
|
externalId: msg.ts,
|
|
5737
5983
|
externalType: "message",
|
|
5738
5984
|
externalSource: "slack",
|
|
5739
|
-
externalUrl: msg.permalink
|
|
5985
|
+
externalUrl: msg.permalink,
|
|
5986
|
+
// Direct messages have no channel name to tag
|
|
5987
|
+
sourceName: msg.channel?.name || void 0
|
|
5740
5988
|
};
|
|
5741
5989
|
}
|
|
5742
5990
|
};
|
|
@@ -5815,6 +6063,22 @@ var AdapterFactory = class {
|
|
|
5815
6063
|
// src/commands/sync.ts
|
|
5816
6064
|
init_logger();
|
|
5817
6065
|
|
|
6066
|
+
// src/utils/external-source.ts
|
|
6067
|
+
init_esm_shims();
|
|
6068
|
+
function getGitExternalSource(source) {
|
|
6069
|
+
switch (source) {
|
|
6070
|
+
case "github":
|
|
6071
|
+
return "github";
|
|
6072
|
+
case "gitlab":
|
|
6073
|
+
return "gitlab";
|
|
6074
|
+
case "bitbucket":
|
|
6075
|
+
case "atlassian":
|
|
6076
|
+
return "bitbucket";
|
|
6077
|
+
default:
|
|
6078
|
+
return void 0;
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
|
|
5818
6082
|
// src/utils/auth-helper.ts
|
|
5819
6083
|
init_esm_shims();
|
|
5820
6084
|
init_auth_service();
|
|
@@ -6472,6 +6736,17 @@ init_api_service();
|
|
|
6472
6736
|
init_auth_service();
|
|
6473
6737
|
init_storage_service();
|
|
6474
6738
|
init_logger();
|
|
6739
|
+
|
|
6740
|
+
// src/utils/source-tag.ts
|
|
6741
|
+
init_esm_shims();
|
|
6742
|
+
var MAX_SOURCE_TAG_LENGTH = 50;
|
|
6743
|
+
function withSourceTag(sourceName, tags) {
|
|
6744
|
+
const tag = sourceName?.replace(/,/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_SOURCE_TAG_LENGTH).trim();
|
|
6745
|
+
if (!tag) return tags;
|
|
6746
|
+
return [tag, ...tags.filter((existing) => existing.toLowerCase() !== tag.toLowerCase())];
|
|
6747
|
+
}
|
|
6748
|
+
|
|
6749
|
+
// src/sync/multi-repo-sync.ts
|
|
6475
6750
|
async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, options) {
|
|
6476
6751
|
const TOTAL_STEPS = 5;
|
|
6477
6752
|
logger.debug(`Syncing repository: ${repo.name} at ${repo.path}`);
|
|
@@ -6629,8 +6904,7 @@ async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, opt
|
|
|
6629
6904
|
mode: options.turbo ? "turbo" : "manual",
|
|
6630
6905
|
brags: acceptedBrags.map((refined, index) => {
|
|
6631
6906
|
const originalCommit = selectedCommits[index];
|
|
6632
|
-
const
|
|
6633
|
-
const tagsWithRepo = refined.suggested_tags.includes(repoTag) ? refined.suggested_tags : [repoTag, ...refined.suggested_tags];
|
|
6907
|
+
const tagsWithRepo = withSourceTag(repo.name, refined.suggested_tags);
|
|
6634
6908
|
return {
|
|
6635
6909
|
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
6636
6910
|
title: refined.refined_title,
|
|
@@ -6649,7 +6923,7 @@ async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, opt
|
|
|
6649
6923
|
// External fields for non-git sources
|
|
6650
6924
|
externalId: originalCommit?.externalId,
|
|
6651
6925
|
externalType: originalCommit?.externalType,
|
|
6652
|
-
externalSource: originalCommit?.externalSource,
|
|
6926
|
+
externalSource: originalCommit?.externalSource ?? getGitExternalSource(repo.service),
|
|
6653
6927
|
externalUrl: originalCommit?.externalUrl
|
|
6654
6928
|
};
|
|
6655
6929
|
})
|
|
@@ -7059,11 +7333,10 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
|
|
|
7059
7333
|
brags: acceptedBrags.map((refined, index) => {
|
|
7060
7334
|
const originalCommit = selectedCommits[index];
|
|
7061
7335
|
const isGitSource = sourceType === "github" || sourceType === "gitlab" || sourceType === "bitbucket" || sourceType === "atlassian";
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
}
|
|
7336
|
+
const tagsWithRepo = withSourceTag(
|
|
7337
|
+
isGitSource ? repoInfo.name : originalCommit?.sourceName,
|
|
7338
|
+
refined.suggested_tags
|
|
7339
|
+
);
|
|
7067
7340
|
return {
|
|
7068
7341
|
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
7069
7342
|
title: refined.refined_title,
|
|
@@ -7081,7 +7354,7 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
|
|
|
7081
7354
|
// External fields for non-git sources (Jira, Confluence, etc.)
|
|
7082
7355
|
externalId: originalCommit?.externalId,
|
|
7083
7356
|
externalType: originalCommit?.externalType,
|
|
7084
|
-
externalSource: originalCommit?.externalSource,
|
|
7357
|
+
externalSource: originalCommit?.externalSource ?? getGitExternalSource(sourceType),
|
|
7085
7358
|
externalUrl: originalCommit?.externalUrl
|
|
7086
7359
|
};
|
|
7087
7360
|
})
|
|
@@ -7461,7 +7734,8 @@ To authenticate:
|
|
|
7461
7734
|
sourceType = selectedSource;
|
|
7462
7735
|
}
|
|
7463
7736
|
if (sourceType === "jira" || sourceType === "confluence") {
|
|
7464
|
-
const
|
|
7737
|
+
const localCreds = await storageService.getServiceCredentials(sourceType);
|
|
7738
|
+
const creds = localCreds?.accessToken ? localCreds : await integrationsService.getAtlassianCredentials(sourceType);
|
|
7465
7739
|
const envInstance = loadEnvConfig()[`${sourceType}Instance`];
|
|
7466
7740
|
const projectConfig = await findProjectConfig();
|
|
7467
7741
|
const configInstance = projectConfig?.[`${sourceType}Instance`];
|
|
@@ -7564,6 +7838,9 @@ function getErrorHint(error, sourceType) {
|
|
|
7564
7838
|
}
|
|
7565
7839
|
return 'Run "bragduck auth login" to login again';
|
|
7566
7840
|
}
|
|
7841
|
+
if (error.name === "IntegrationError" && error.details?.hint) {
|
|
7842
|
+
return error.details.hint;
|
|
7843
|
+
}
|
|
7567
7844
|
if (error.name === "GoogleDocsError") {
|
|
7568
7845
|
return 'Run "bragduck auth google" to re-authenticate with Google';
|
|
7569
7846
|
}
|