@bragduck/cli 2.8.1 → 2.8.3
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 +378 -468
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -1222,7 +1222,9 @@ var init_auth_service = __esm({
|
|
|
1222
1222
|
} catch (error) {
|
|
1223
1223
|
logger.debug(`Token refresh failed: ${error.message}`);
|
|
1224
1224
|
await this.logout();
|
|
1225
|
-
throw new AuthenticationError(
|
|
1225
|
+
throw new AuthenticationError(
|
|
1226
|
+
'Bragduck platform token refresh failed. Please run "bragduck auth login" to re-authenticate.'
|
|
1227
|
+
);
|
|
1226
1228
|
}
|
|
1227
1229
|
}
|
|
1228
1230
|
};
|
|
@@ -1428,7 +1430,7 @@ var init_api_service = __esm({
|
|
|
1428
1430
|
throw error;
|
|
1429
1431
|
}
|
|
1430
1432
|
throw new TokenExpiredError(
|
|
1431
|
-
'Your session has expired. Please run "bragduck
|
|
1433
|
+
'Your Bragduck platform session has expired. Please run "bragduck auth login" to re-authenticate.'
|
|
1432
1434
|
);
|
|
1433
1435
|
}
|
|
1434
1436
|
}
|
|
@@ -2177,188 +2179,10 @@ var CancelPromptError = class extends Error {
|
|
|
2177
2179
|
init_api_service();
|
|
2178
2180
|
init_storage_service();
|
|
2179
2181
|
init_auth_service();
|
|
2180
|
-
import boxen6 from "boxen";
|
|
2181
|
-
|
|
2182
|
-
// src/utils/source-detector.ts
|
|
2183
|
-
init_esm_shims();
|
|
2184
|
-
init_errors();
|
|
2185
|
-
init_storage_service();
|
|
2186
|
-
import { exec as exec2 } from "child_process";
|
|
2187
|
-
import { promisify as promisify2 } from "util";
|
|
2188
|
-
import { select } from "@inquirer/prompts";
|
|
2189
|
-
var execAsync2 = promisify2(exec2);
|
|
2190
|
-
var SourceDetector = class {
|
|
2191
|
-
/**
|
|
2192
|
-
* Detect all possible sources from git remotes
|
|
2193
|
-
*/
|
|
2194
|
-
async detectSources(options = {}) {
|
|
2195
|
-
const detected = [];
|
|
2196
|
-
try {
|
|
2197
|
-
const { stdout } = await execAsync2("git remote -v");
|
|
2198
|
-
const remotes = this.parseRemotes(stdout);
|
|
2199
|
-
for (const remote of remotes) {
|
|
2200
|
-
const source = this.parseRemoteUrl(remote.url);
|
|
2201
|
-
if (source) {
|
|
2202
|
-
const isAuthenticated = await this.checkAuthentication(source.type);
|
|
2203
|
-
detected.push({
|
|
2204
|
-
...source,
|
|
2205
|
-
remoteUrl: remote.url,
|
|
2206
|
-
isAuthenticated
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
} catch {
|
|
2211
|
-
throw new GitError("Not a git repository");
|
|
2212
|
-
}
|
|
2213
|
-
let recommended;
|
|
2214
|
-
if (detected.length > 1 && options.allowInteractive && process.stdout.isTTY) {
|
|
2215
|
-
try {
|
|
2216
|
-
recommended = await this.promptSourceSelection(detected, options.showAuthStatus);
|
|
2217
|
-
} catch {
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
if (!recommended && options.respectPriority) {
|
|
2221
|
-
const priority = await storageService.getConfigWithHierarchy("sourcePriority");
|
|
2222
|
-
if (priority) {
|
|
2223
|
-
recommended = this.applyPriority(detected, priority);
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
if (!recommended) {
|
|
2227
|
-
recommended = this.selectRecommendedSource(detected);
|
|
2228
|
-
}
|
|
2229
|
-
return { detected, recommended };
|
|
2230
|
-
}
|
|
2231
|
-
/**
|
|
2232
|
-
* Prompt user to select a source interactively
|
|
2233
|
-
*/
|
|
2234
|
-
async promptSourceSelection(sources, showAuthStatus = true) {
|
|
2235
|
-
const choices = sources.map((source) => {
|
|
2236
|
-
const authStatus2 = showAuthStatus ? source.isAuthenticated ? "\u2713 authenticated" : "\u2717 not authenticated" : "";
|
|
2237
|
-
const repo = source.owner && source.repo ? `${source.owner}/${source.repo}` : source.host;
|
|
2238
|
-
const name = `${source.type}${authStatus2 ? ` (${authStatus2})` : ""} - ${repo}`;
|
|
2239
|
-
return {
|
|
2240
|
-
name,
|
|
2241
|
-
value: source.type,
|
|
2242
|
-
description: source.remoteUrl
|
|
2243
|
-
};
|
|
2244
|
-
});
|
|
2245
|
-
return await select({
|
|
2246
|
-
message: "Multiple sources detected. Which do you want to sync?",
|
|
2247
|
-
choices
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
|
-
/**
|
|
2251
|
-
* Apply configured priority to select source
|
|
2252
|
-
*/
|
|
2253
|
-
applyPriority(sources, priority) {
|
|
2254
|
-
for (const sourceType of priority) {
|
|
2255
|
-
const found = sources.find((s) => s.type === sourceType);
|
|
2256
|
-
if (found) return found.type;
|
|
2257
|
-
}
|
|
2258
|
-
return void 0;
|
|
2259
|
-
}
|
|
2260
|
-
/**
|
|
2261
|
-
* Parse git remote -v output
|
|
2262
|
-
*/
|
|
2263
|
-
parseRemotes(output) {
|
|
2264
|
-
const lines = output.split("\n").filter(Boolean);
|
|
2265
|
-
const remotes = /* @__PURE__ */ new Map();
|
|
2266
|
-
for (const line of lines) {
|
|
2267
|
-
const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
|
|
2268
|
-
if (match && match[1] && match[2]) {
|
|
2269
|
-
remotes.set(match[1], match[2]);
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
return Array.from(remotes.entries()).map(([name, url]) => ({ name, url }));
|
|
2273
|
-
}
|
|
2274
|
-
/**
|
|
2275
|
-
* Parse remote URL to detect source type
|
|
2276
|
-
*/
|
|
2277
|
-
parseRemoteUrl(url) {
|
|
2278
|
-
if (url.includes("github.com")) {
|
|
2279
|
-
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
2280
|
-
if (match && match[1] && match[2]) {
|
|
2281
|
-
return {
|
|
2282
|
-
type: "github",
|
|
2283
|
-
host: "github.com",
|
|
2284
|
-
owner: match[1],
|
|
2285
|
-
repo: match[2]
|
|
2286
|
-
};
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
if (url.includes("gitlab.com")) {
|
|
2290
|
-
const match = url.match(/gitlab\.com[:/]([^/]+)\/([^/.]+)/);
|
|
2291
|
-
if (match && match[1] && match[2]) {
|
|
2292
|
-
return {
|
|
2293
|
-
type: "gitlab",
|
|
2294
|
-
host: "gitlab.com",
|
|
2295
|
-
owner: match[1],
|
|
2296
|
-
repo: match[2]
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
if (url.includes("bitbucket.org")) {
|
|
2301
|
-
const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
|
|
2302
|
-
if (match && match[1] && match[2]) {
|
|
2303
|
-
return {
|
|
2304
|
-
type: "bitbucket",
|
|
2305
|
-
host: "bitbucket.org",
|
|
2306
|
-
owner: match[1],
|
|
2307
|
-
repo: match[2]
|
|
2308
|
-
};
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
if (url.match(/\/scm\/|bitbucket\./)) {
|
|
2312
|
-
const match = url.match(/([^/:]+)[:/]scm\/([^/]+)\/([^/.]+)/);
|
|
2313
|
-
if (match && match[1] && match[2] && match[3]) {
|
|
2314
|
-
return {
|
|
2315
|
-
type: "atlassian",
|
|
2316
|
-
host: match[1],
|
|
2317
|
-
owner: match[2],
|
|
2318
|
-
repo: match[3]
|
|
2319
|
-
};
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
return null;
|
|
2323
|
-
}
|
|
2324
|
-
/**
|
|
2325
|
-
* Check if user is authenticated with a specific source
|
|
2326
|
-
*/
|
|
2327
|
-
async checkAuthentication(type) {
|
|
2328
|
-
try {
|
|
2329
|
-
if (type === "github") {
|
|
2330
|
-
await execAsync2("command gh auth status");
|
|
2331
|
-
return true;
|
|
2332
|
-
} else if (type === "bitbucket" || type === "atlassian") {
|
|
2333
|
-
return await storageService.isServiceAuthenticated("bitbucket");
|
|
2334
|
-
} else if (type === "gitlab") {
|
|
2335
|
-
return await storageService.isServiceAuthenticated("gitlab");
|
|
2336
|
-
} else if (type === "jira") {
|
|
2337
|
-
return await storageService.isServiceAuthenticated("jira");
|
|
2338
|
-
} else if (type === "confluence") {
|
|
2339
|
-
return await storageService.isServiceAuthenticated("confluence");
|
|
2340
|
-
}
|
|
2341
|
-
return false;
|
|
2342
|
-
} catch {
|
|
2343
|
-
return false;
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Select recommended source (prefer authenticated GitHub)
|
|
2348
|
-
*/
|
|
2349
|
-
selectRecommendedSource(sources) {
|
|
2350
|
-
const authenticated = sources.find((s) => s.isAuthenticated);
|
|
2351
|
-
if (authenticated) return authenticated.type;
|
|
2352
|
-
const github = sources.find((s) => s.type === "github");
|
|
2353
|
-
if (github) return "github";
|
|
2354
|
-
return sources[0]?.type;
|
|
2355
|
-
}
|
|
2356
|
-
};
|
|
2357
|
-
var sourceDetector = new SourceDetector();
|
|
2358
|
-
|
|
2359
|
-
// src/commands/sync.ts
|
|
2360
2182
|
init_env_loader();
|
|
2361
2183
|
init_config_loader();
|
|
2184
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
2185
|
+
import boxen6 from "boxen";
|
|
2362
2186
|
|
|
2363
2187
|
// src/sync/adapter-factory.ts
|
|
2364
2188
|
init_esm_shims();
|
|
@@ -2370,8 +2194,8 @@ init_esm_shims();
|
|
|
2370
2194
|
init_esm_shims();
|
|
2371
2195
|
init_errors();
|
|
2372
2196
|
init_logger();
|
|
2373
|
-
import { exec as
|
|
2374
|
-
import { promisify as
|
|
2197
|
+
import { exec as exec2 } from "child_process";
|
|
2198
|
+
import { promisify as promisify2 } from "util";
|
|
2375
2199
|
|
|
2376
2200
|
// src/services/git.service.ts
|
|
2377
2201
|
init_esm_shims();
|
|
@@ -2596,7 +2420,7 @@ var GitService = class {
|
|
|
2596
2420
|
var gitService = new GitService();
|
|
2597
2421
|
|
|
2598
2422
|
// src/services/github.service.ts
|
|
2599
|
-
var
|
|
2423
|
+
var execAsync2 = promisify2(exec2);
|
|
2600
2424
|
var GitHubService = class {
|
|
2601
2425
|
MAX_BODY_LENGTH = 5e3;
|
|
2602
2426
|
PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
|
|
@@ -2605,7 +2429,7 @@ var GitHubService = class {
|
|
|
2605
2429
|
*/
|
|
2606
2430
|
async checkGitHubCLI() {
|
|
2607
2431
|
try {
|
|
2608
|
-
await
|
|
2432
|
+
await execAsync2("command gh --version");
|
|
2609
2433
|
return true;
|
|
2610
2434
|
} catch {
|
|
2611
2435
|
return false;
|
|
@@ -2627,7 +2451,7 @@ var GitHubService = class {
|
|
|
2627
2451
|
*/
|
|
2628
2452
|
async checkAuthentication() {
|
|
2629
2453
|
try {
|
|
2630
|
-
await
|
|
2454
|
+
await execAsync2("command gh auth status");
|
|
2631
2455
|
return true;
|
|
2632
2456
|
} catch {
|
|
2633
2457
|
return false;
|
|
@@ -2652,7 +2476,7 @@ var GitHubService = class {
|
|
|
2652
2476
|
await this.ensureGitHubCLI();
|
|
2653
2477
|
await this.ensureAuthentication();
|
|
2654
2478
|
await gitService.validateRepository();
|
|
2655
|
-
const { stdout } = await
|
|
2479
|
+
const { stdout } = await execAsync2("command gh repo view --json url");
|
|
2656
2480
|
const data = JSON.parse(stdout);
|
|
2657
2481
|
if (!data.url) {
|
|
2658
2482
|
throw new GitHubError("This repository is not hosted on GitHub", {
|
|
@@ -2684,7 +2508,7 @@ var GitHubService = class {
|
|
|
2684
2508
|
async getRepositoryInfo() {
|
|
2685
2509
|
try {
|
|
2686
2510
|
await this.ensureGitHubCLI();
|
|
2687
|
-
const { stdout } = await
|
|
2511
|
+
const { stdout } = await execAsync2(
|
|
2688
2512
|
"command gh repo view --json owner,name,url,nameWithOwner"
|
|
2689
2513
|
);
|
|
2690
2514
|
const data = JSON.parse(stdout);
|
|
@@ -2709,7 +2533,7 @@ var GitHubService = class {
|
|
|
2709
2533
|
*/
|
|
2710
2534
|
async getCurrentGitHubUser() {
|
|
2711
2535
|
try {
|
|
2712
|
-
const { stdout } = await
|
|
2536
|
+
const { stdout } = await execAsync2("command gh api user --jq .login");
|
|
2713
2537
|
return stdout.trim() || null;
|
|
2714
2538
|
} catch {
|
|
2715
2539
|
logger.debug("Failed to get GitHub user");
|
|
@@ -2735,7 +2559,7 @@ var GitHubService = class {
|
|
|
2735
2559
|
const limitArg = limit ? `--limit ${limit}` : "";
|
|
2736
2560
|
const command = `command gh pr list --state merged --json ${this.PR_SEARCH_FIELDS} --search "${searchQuery}" ${limitArg}`;
|
|
2737
2561
|
logger.debug(`Running: ${command}`);
|
|
2738
|
-
const { stdout } = await
|
|
2562
|
+
const { stdout } = await execAsync2(command);
|
|
2739
2563
|
const prs = JSON.parse(stdout);
|
|
2740
2564
|
logger.debug(`Found ${prs.length} merged PRs`);
|
|
2741
2565
|
return prs;
|
|
@@ -2846,9 +2670,9 @@ init_esm_shims();
|
|
|
2846
2670
|
init_errors();
|
|
2847
2671
|
init_logger();
|
|
2848
2672
|
init_storage_service();
|
|
2849
|
-
import { exec as
|
|
2850
|
-
import { promisify as
|
|
2851
|
-
var
|
|
2673
|
+
import { exec as exec3 } from "child_process";
|
|
2674
|
+
import { promisify as promisify3 } from "util";
|
|
2675
|
+
var execAsync3 = promisify3(exec3);
|
|
2852
2676
|
var BitbucketService = class {
|
|
2853
2677
|
BITBUCKET_API_BASE = "https://api.bitbucket.org/2.0";
|
|
2854
2678
|
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
@@ -2933,7 +2757,7 @@ var BitbucketService = class {
|
|
|
2933
2757
|
*/
|
|
2934
2758
|
async getRepoFromGit() {
|
|
2935
2759
|
try {
|
|
2936
|
-
const { stdout } = await
|
|
2760
|
+
const { stdout } = await execAsync3("command git remote get-url origin");
|
|
2937
2761
|
const remoteUrl = stdout.trim();
|
|
2938
2762
|
const parsed = this.parseRemoteUrl(remoteUrl);
|
|
2939
2763
|
if (!parsed) {
|
|
@@ -3128,10 +2952,10 @@ init_esm_shims();
|
|
|
3128
2952
|
init_errors();
|
|
3129
2953
|
init_logger();
|
|
3130
2954
|
init_storage_service();
|
|
3131
|
-
import { exec as
|
|
3132
|
-
import { promisify as
|
|
2955
|
+
import { exec as exec4 } from "child_process";
|
|
2956
|
+
import { promisify as promisify4 } from "util";
|
|
3133
2957
|
import { URLSearchParams as URLSearchParams3 } from "url";
|
|
3134
|
-
var
|
|
2958
|
+
var execAsync4 = promisify4(exec4);
|
|
3135
2959
|
var GitLabService = class {
|
|
3136
2960
|
DEFAULT_INSTANCE = "https://gitlab.com";
|
|
3137
2961
|
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
@@ -3217,7 +3041,7 @@ var GitLabService = class {
|
|
|
3217
3041
|
*/
|
|
3218
3042
|
async getProjectFromGit() {
|
|
3219
3043
|
try {
|
|
3220
|
-
const { stdout } = await
|
|
3044
|
+
const { stdout } = await execAsync4("git remote get-url origin");
|
|
3221
3045
|
const remoteUrl = stdout.trim();
|
|
3222
3046
|
const parsed = this.parseRemoteUrl(remoteUrl);
|
|
3223
3047
|
if (!parsed) {
|
|
@@ -4109,7 +3933,7 @@ init_logger();
|
|
|
4109
3933
|
|
|
4110
3934
|
// src/ui/prompts.ts
|
|
4111
3935
|
init_esm_shims();
|
|
4112
|
-
import { checkbox, confirm, input as input2, select
|
|
3936
|
+
import { checkbox, confirm, input as input2, select, editor } from "@inquirer/prompts";
|
|
4113
3937
|
import boxen4 from "boxen";
|
|
4114
3938
|
|
|
4115
3939
|
// src/ui/formatters.ts
|
|
@@ -4265,7 +4089,7 @@ async function promptDaysToScan(defaultDays = 30) {
|
|
|
4265
4089
|
{ name: "90 days", value: "90", description: "Last 3 months" },
|
|
4266
4090
|
{ name: "Custom", value: "custom", description: "Enter custom number of days" }
|
|
4267
4091
|
];
|
|
4268
|
-
const selected = await
|
|
4092
|
+
const selected = await select({
|
|
4269
4093
|
message: "How many days back should we scan for PRs?",
|
|
4270
4094
|
choices,
|
|
4271
4095
|
default: "30"
|
|
@@ -4297,7 +4121,7 @@ async function promptSortOption() {
|
|
|
4297
4121
|
{ name: "By files (most files)", value: "files", description: "Most files changed" },
|
|
4298
4122
|
{ name: "No sorting", value: "none", description: "Keep original order" }
|
|
4299
4123
|
];
|
|
4300
|
-
return await
|
|
4124
|
+
return await select({
|
|
4301
4125
|
message: "How would you like to sort the PRs?",
|
|
4302
4126
|
choices,
|
|
4303
4127
|
default: "date"
|
|
@@ -4311,7 +4135,7 @@ async function promptSelectOrganisation(organisations) {
|
|
|
4311
4135
|
value: org.id
|
|
4312
4136
|
}))
|
|
4313
4137
|
];
|
|
4314
|
-
const selected = await
|
|
4138
|
+
const selected = await select({
|
|
4315
4139
|
message: "Attach brags to which company?",
|
|
4316
4140
|
choices,
|
|
4317
4141
|
default: "none"
|
|
@@ -4361,7 +4185,7 @@ ${theme.label("PR Link")} ${colors.link(prUrl)}`;
|
|
|
4361
4185
|
}
|
|
4362
4186
|
console.log(boxen4(bragDetails, boxStyles.info));
|
|
4363
4187
|
console.log("");
|
|
4364
|
-
const action = await
|
|
4188
|
+
const action = await select({
|
|
4365
4189
|
message: `What would you like to do with this brag?`,
|
|
4366
4190
|
choices: [
|
|
4367
4191
|
{ name: "\u2713 Accept", value: "accept", description: "Add this brag as-is" },
|
|
@@ -4446,7 +4270,7 @@ async function ensureAuthenticated() {
|
|
|
4446
4270
|
if (!shouldAuth) {
|
|
4447
4271
|
logger.log("");
|
|
4448
4272
|
logger.info(
|
|
4449
|
-
theme.secondary("Authentication skipped. Run ") + theme.command("bragduck
|
|
4273
|
+
theme.secondary("Authentication skipped. Run ") + theme.command("bragduck auth login") + theme.secondary(" when you're ready to authenticate.")
|
|
4450
4274
|
);
|
|
4451
4275
|
logger.log("");
|
|
4452
4276
|
return false;
|
|
@@ -4459,9 +4283,6 @@ async function ensureAuthenticated() {
|
|
|
4459
4283
|
}
|
|
4460
4284
|
}
|
|
4461
4285
|
|
|
4462
|
-
// src/commands/sync.ts
|
|
4463
|
-
init_errors();
|
|
4464
|
-
|
|
4465
4286
|
// src/ui/spinners.ts
|
|
4466
4287
|
init_esm_shims();
|
|
4467
4288
|
import ora2 from "ora";
|
|
@@ -4507,9 +4328,313 @@ function failStepSpinner(spinner, currentStep, totalSteps, text) {
|
|
|
4507
4328
|
}
|
|
4508
4329
|
|
|
4509
4330
|
// src/commands/sync.ts
|
|
4331
|
+
async function promptSelectService() {
|
|
4332
|
+
const allServices = [
|
|
4333
|
+
"github",
|
|
4334
|
+
"gitlab",
|
|
4335
|
+
"bitbucket",
|
|
4336
|
+
"atlassian",
|
|
4337
|
+
"jira",
|
|
4338
|
+
"confluence"
|
|
4339
|
+
];
|
|
4340
|
+
const authenticatedServices = await storageService.getAuthenticatedServices();
|
|
4341
|
+
const authenticatedSyncServices = authenticatedServices.filter(
|
|
4342
|
+
(service) => service !== "bragduck" && allServices.includes(service)
|
|
4343
|
+
);
|
|
4344
|
+
const serviceChoices = await Promise.all(
|
|
4345
|
+
allServices.map(async (service) => {
|
|
4346
|
+
const isAuth = await storageService.isServiceAuthenticated(service);
|
|
4347
|
+
const indicator = isAuth ? "\u2713" : "\u2717";
|
|
4348
|
+
const serviceLabel = service.charAt(0).toUpperCase() + service.slice(1);
|
|
4349
|
+
return {
|
|
4350
|
+
name: `${indicator} ${serviceLabel}`,
|
|
4351
|
+
value: service,
|
|
4352
|
+
description: isAuth ? "Authenticated" : "Not authenticated"
|
|
4353
|
+
};
|
|
4354
|
+
})
|
|
4355
|
+
);
|
|
4356
|
+
if (authenticatedSyncServices.length > 0) {
|
|
4357
|
+
const serviceNames = authenticatedSyncServices.map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(", ");
|
|
4358
|
+
serviceChoices.push({
|
|
4359
|
+
name: `\u2713 All authenticated services (${serviceNames})`,
|
|
4360
|
+
value: "all",
|
|
4361
|
+
description: `Sync all ${authenticatedSyncServices.length} authenticated service${authenticatedSyncServices.length > 1 ? "s" : ""}`
|
|
4362
|
+
});
|
|
4363
|
+
}
|
|
4364
|
+
const selected = await select2({
|
|
4365
|
+
message: "Select a service to sync:",
|
|
4366
|
+
choices: serviceChoices
|
|
4367
|
+
});
|
|
4368
|
+
return selected;
|
|
4369
|
+
}
|
|
4370
|
+
async function syncSingleService(sourceType, options, TOTAL_STEPS) {
|
|
4371
|
+
const adapter = AdapterFactory.getAdapter(sourceType);
|
|
4372
|
+
const repoSpinner = createStepSpinner(2, TOTAL_STEPS, "Validating repository");
|
|
4373
|
+
repoSpinner.start();
|
|
4374
|
+
await adapter.validate();
|
|
4375
|
+
const repoInfo = await adapter.getRepositoryInfo();
|
|
4376
|
+
succeedStepSpinner(repoSpinner, 2, TOTAL_STEPS, `Repository: ${theme.value(repoInfo.fullName)}`);
|
|
4377
|
+
logger.log("");
|
|
4378
|
+
let days = options.days;
|
|
4379
|
+
if (!days) {
|
|
4380
|
+
const defaultDays = storageService.getConfig("defaultCommitDays");
|
|
4381
|
+
days = await promptDaysToScan(defaultDays);
|
|
4382
|
+
logger.log("");
|
|
4383
|
+
}
|
|
4384
|
+
const fetchSpinner = createStepSpinner(
|
|
4385
|
+
3,
|
|
4386
|
+
TOTAL_STEPS,
|
|
4387
|
+
`Fetching work items from the last ${days} days`
|
|
4388
|
+
);
|
|
4389
|
+
fetchSpinner.start();
|
|
4390
|
+
const workItems = await adapter.fetchWorkItems({
|
|
4391
|
+
days,
|
|
4392
|
+
author: options.all ? void 0 : await adapter.getCurrentUser() || void 0
|
|
4393
|
+
});
|
|
4394
|
+
if (workItems.length === 0) {
|
|
4395
|
+
failStepSpinner(fetchSpinner, 3, TOTAL_STEPS, `No work items found in the last ${days} days`);
|
|
4396
|
+
logger.log("");
|
|
4397
|
+
logger.info("Try increasing the number of days or check your activity");
|
|
4398
|
+
return { created: 0, skipped: 0 };
|
|
4399
|
+
}
|
|
4400
|
+
succeedStepSpinner(
|
|
4401
|
+
fetchSpinner,
|
|
4402
|
+
3,
|
|
4403
|
+
TOTAL_STEPS,
|
|
4404
|
+
`Found ${theme.count(workItems.length)} work item${workItems.length > 1 ? "s" : ""}`
|
|
4405
|
+
);
|
|
4406
|
+
logger.log("");
|
|
4407
|
+
logger.log(formatCommitStats(workItems));
|
|
4408
|
+
logger.log("");
|
|
4409
|
+
let sortedCommits = [...workItems];
|
|
4410
|
+
if (workItems.length > 1) {
|
|
4411
|
+
const sortOption = await promptSortOption();
|
|
4412
|
+
logger.log("");
|
|
4413
|
+
if (sortOption === "date") {
|
|
4414
|
+
sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
4415
|
+
} else if (sortOption === "size") {
|
|
4416
|
+
sortedCommits.sort((a, b) => {
|
|
4417
|
+
const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
|
|
4418
|
+
const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
|
|
4419
|
+
return sizeB - sizeA;
|
|
4420
|
+
});
|
|
4421
|
+
} else if (sortOption === "files") {
|
|
4422
|
+
sortedCommits.sort((a, b) => {
|
|
4423
|
+
const filesA = a.diffStats?.filesChanged || 0;
|
|
4424
|
+
const filesB = b.diffStats?.filesChanged || 0;
|
|
4425
|
+
return filesB - filesA;
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
const selectedShas = await promptSelectCommits(sortedCommits);
|
|
4430
|
+
if (selectedShas.length === 0) {
|
|
4431
|
+
logger.log("");
|
|
4432
|
+
logger.info(theme.secondary("No work items selected. Sync cancelled."));
|
|
4433
|
+
logger.log("");
|
|
4434
|
+
return { created: 0, skipped: 0 };
|
|
4435
|
+
}
|
|
4436
|
+
const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
|
|
4437
|
+
logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
|
|
4438
|
+
logger.log("");
|
|
4439
|
+
const existingBrags = await apiService.listBrags({ limit: 100 });
|
|
4440
|
+
logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
|
|
4441
|
+
const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
|
|
4442
|
+
logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
|
|
4443
|
+
const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
|
|
4444
|
+
const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
|
|
4445
|
+
logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
|
|
4446
|
+
if (duplicates.length > 0) {
|
|
4447
|
+
logger.log("");
|
|
4448
|
+
logger.info(
|
|
4449
|
+
colors.warning(
|
|
4450
|
+
`${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
|
|
4451
|
+
)
|
|
4452
|
+
);
|
|
4453
|
+
logger.log("");
|
|
4454
|
+
}
|
|
4455
|
+
if (newCommits.length === 0) {
|
|
4456
|
+
logger.log("");
|
|
4457
|
+
logger.info(
|
|
4458
|
+
theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
|
|
4459
|
+
);
|
|
4460
|
+
logger.log("");
|
|
4461
|
+
return { created: 0, skipped: duplicates.length };
|
|
4462
|
+
}
|
|
4463
|
+
const refineSpinner = createStepSpinner(
|
|
4464
|
+
4,
|
|
4465
|
+
TOTAL_STEPS,
|
|
4466
|
+
`Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
|
|
4467
|
+
);
|
|
4468
|
+
refineSpinner.start();
|
|
4469
|
+
const refineRequest = {
|
|
4470
|
+
brags: newCommits.map((c) => ({
|
|
4471
|
+
text: c.message,
|
|
4472
|
+
date: c.date,
|
|
4473
|
+
title: c.message.split("\n")[0]
|
|
4474
|
+
}))
|
|
4475
|
+
};
|
|
4476
|
+
const refineResponse = await apiService.refineBrags(refineRequest);
|
|
4477
|
+
let refinedBrags = refineResponse.refined_brags;
|
|
4478
|
+
succeedStepSpinner(refineSpinner, 4, TOTAL_STEPS, "Work items refined successfully");
|
|
4479
|
+
logger.log("");
|
|
4480
|
+
logger.info("Preview of refined brags:");
|
|
4481
|
+
logger.log("");
|
|
4482
|
+
logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
|
|
4483
|
+
logger.log("");
|
|
4484
|
+
const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
|
|
4485
|
+
if (acceptedBrags.length === 0) {
|
|
4486
|
+
logger.log("");
|
|
4487
|
+
logger.info(theme.secondary("No brags selected for creation. Sync cancelled."));
|
|
4488
|
+
logger.log("");
|
|
4489
|
+
return { created: 0, skipped: duplicates.length };
|
|
4490
|
+
}
|
|
4491
|
+
logger.log("");
|
|
4492
|
+
let selectedOrgId = null;
|
|
4493
|
+
const userInfo = authService.getUserInfo();
|
|
4494
|
+
if (userInfo?.id) {
|
|
4495
|
+
try {
|
|
4496
|
+
const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
|
|
4497
|
+
if (orgsResponse.items.length > 0) {
|
|
4498
|
+
selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
|
|
4499
|
+
logger.log("");
|
|
4500
|
+
}
|
|
4501
|
+
} catch {
|
|
4502
|
+
logger.debug("Failed to fetch organisations, skipping org selection");
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
const createSpinner2 = createStepSpinner(
|
|
4506
|
+
5,
|
|
4507
|
+
TOTAL_STEPS,
|
|
4508
|
+
`Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
|
|
4509
|
+
);
|
|
4510
|
+
createSpinner2.start();
|
|
4511
|
+
const createRequest = {
|
|
4512
|
+
brags: acceptedBrags.map((refined, index) => {
|
|
4513
|
+
const originalCommit = newCommits[index];
|
|
4514
|
+
return {
|
|
4515
|
+
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
4516
|
+
title: refined.refined_title,
|
|
4517
|
+
description: refined.refined_description,
|
|
4518
|
+
tags: refined.suggested_tags,
|
|
4519
|
+
repository: repoInfo.url,
|
|
4520
|
+
date: originalCommit?.date || "",
|
|
4521
|
+
commit_url: originalCommit?.url || "",
|
|
4522
|
+
impact_score: refined.suggested_impactLevel,
|
|
4523
|
+
impact_description: refined.impact_description,
|
|
4524
|
+
attachments: originalCommit?.url ? [originalCommit.url] : [],
|
|
4525
|
+
orgId: selectedOrgId || void 0,
|
|
4526
|
+
// External fields for non-git sources (Jira, Confluence, etc.)
|
|
4527
|
+
externalId: originalCommit?.externalId,
|
|
4528
|
+
externalType: originalCommit?.externalType,
|
|
4529
|
+
externalSource: originalCommit?.externalSource,
|
|
4530
|
+
externalUrl: originalCommit?.externalUrl
|
|
4531
|
+
};
|
|
4532
|
+
})
|
|
4533
|
+
};
|
|
4534
|
+
const createResponse = await apiService.createBrags(createRequest);
|
|
4535
|
+
succeedStepSpinner(
|
|
4536
|
+
createSpinner2,
|
|
4537
|
+
5,
|
|
4538
|
+
TOTAL_STEPS,
|
|
4539
|
+
`Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
|
|
4540
|
+
);
|
|
4541
|
+
logger.log("");
|
|
4542
|
+
return { created: createResponse.created, skipped: duplicates.length };
|
|
4543
|
+
}
|
|
4544
|
+
async function syncAllAuthenticatedServices(options) {
|
|
4545
|
+
const TOTAL_STEPS = 5;
|
|
4546
|
+
const allServices = [
|
|
4547
|
+
"github",
|
|
4548
|
+
"gitlab",
|
|
4549
|
+
"bitbucket",
|
|
4550
|
+
"atlassian",
|
|
4551
|
+
"jira",
|
|
4552
|
+
"confluence"
|
|
4553
|
+
];
|
|
4554
|
+
const authenticatedServices = await storageService.getAuthenticatedServices();
|
|
4555
|
+
const servicesToSync = authenticatedServices.filter(
|
|
4556
|
+
(service) => service !== "bragduck" && allServices.includes(service)
|
|
4557
|
+
);
|
|
4558
|
+
if (servicesToSync.length === 0) {
|
|
4559
|
+
logger.log("");
|
|
4560
|
+
logger.error("No authenticated services found.");
|
|
4561
|
+
logger.log("");
|
|
4562
|
+
logger.info("Authenticate a service first:");
|
|
4563
|
+
logger.info(` ${theme.command("bragduck auth github")}`);
|
|
4564
|
+
logger.info(` ${theme.command("bragduck auth atlassian")}`);
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
logger.info(
|
|
4568
|
+
theme.highlight(
|
|
4569
|
+
`Syncing ${servicesToSync.length} authenticated service${servicesToSync.length > 1 ? "s" : ""}...`
|
|
4570
|
+
)
|
|
4571
|
+
);
|
|
4572
|
+
logger.log("");
|
|
4573
|
+
const results = [];
|
|
4574
|
+
for (let i = 0; i < servicesToSync.length; i++) {
|
|
4575
|
+
const service = servicesToSync[i];
|
|
4576
|
+
const serviceLabel = service.charAt(0).toUpperCase() + service.slice(1);
|
|
4577
|
+
logger.log(
|
|
4578
|
+
colors.highlight(`\u2501\u2501\u2501 Syncing ${i + 1}/${servicesToSync.length}: ${serviceLabel} \u2501\u2501\u2501`)
|
|
4579
|
+
);
|
|
4580
|
+
logger.log("");
|
|
4581
|
+
try {
|
|
4582
|
+
const result = await syncSingleService(service, options, TOTAL_STEPS);
|
|
4583
|
+
results.push({ service, created: result.created, skipped: result.skipped });
|
|
4584
|
+
logger.log("");
|
|
4585
|
+
} catch (error) {
|
|
4586
|
+
const err = error;
|
|
4587
|
+
logger.log("");
|
|
4588
|
+
logger.warn(`Failed to sync ${serviceLabel}: ${err.message}`);
|
|
4589
|
+
logger.log("");
|
|
4590
|
+
results.push({ service, created: 0, skipped: 0, error: err.message });
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
logger.log("");
|
|
4594
|
+
logger.log(colors.highlight("\u2501\u2501\u2501 Sync Summary \u2501\u2501\u2501"));
|
|
4595
|
+
logger.log("");
|
|
4596
|
+
const successful = results.filter((r) => !r.error);
|
|
4597
|
+
const failed = results.filter((r) => r.error);
|
|
4598
|
+
const totalCreated = successful.reduce((sum, r) => sum + r.created, 0);
|
|
4599
|
+
if (successful.length > 0) {
|
|
4600
|
+
logger.log(
|
|
4601
|
+
theme.success(
|
|
4602
|
+
`\u2713 Successfully synced ${successful.length}/${servicesToSync.length} service${servicesToSync.length > 1 ? "s" : ""}:`
|
|
4603
|
+
)
|
|
4604
|
+
);
|
|
4605
|
+
for (const result of successful) {
|
|
4606
|
+
const serviceLabel = result.service.charAt(0).toUpperCase() + result.service.slice(1);
|
|
4607
|
+
logger.info(
|
|
4608
|
+
` \u2022 ${serviceLabel}: ${result.created} brag${result.created !== 1 ? "s" : ""} created${result.skipped > 0 ? `, ${result.skipped} skipped` : ""}`
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4611
|
+
logger.log("");
|
|
4612
|
+
}
|
|
4613
|
+
if (failed.length > 0) {
|
|
4614
|
+
logger.log(
|
|
4615
|
+
colors.warning(`\u2717 Failed to sync ${failed.length} service${failed.length > 1 ? "s" : ""}:`)
|
|
4616
|
+
);
|
|
4617
|
+
for (const result of failed) {
|
|
4618
|
+
const serviceLabel = result.service.charAt(0).toUpperCase() + result.service.slice(1);
|
|
4619
|
+
logger.info(` \u2022 ${serviceLabel}: ${result.error}`);
|
|
4620
|
+
}
|
|
4621
|
+
logger.log("");
|
|
4622
|
+
}
|
|
4623
|
+
if (totalCreated > 0) {
|
|
4624
|
+
logger.log(boxen6(formatSuccessMessage(totalCreated), boxStyles.success));
|
|
4625
|
+
} else if (successful.length > 0) {
|
|
4626
|
+
logger.log(
|
|
4627
|
+
boxen6(
|
|
4628
|
+
theme.secondary("No new brags created (all items already exist or were skipped)"),
|
|
4629
|
+
boxStyles.info
|
|
4630
|
+
)
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4510
4634
|
async function syncCommand(options = {}) {
|
|
4511
4635
|
logger.log("");
|
|
4512
4636
|
const TOTAL_STEPS = 5;
|
|
4637
|
+
let sourceType;
|
|
4513
4638
|
try {
|
|
4514
4639
|
const isAuthenticated = await ensureAuthenticated();
|
|
4515
4640
|
if (!isAuthenticated) {
|
|
@@ -4535,274 +4660,54 @@ async function syncCommand(options = {}) {
|
|
|
4535
4660
|
return;
|
|
4536
4661
|
}
|
|
4537
4662
|
logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with sync`);
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
const envConfig = loadEnvConfig();
|
|
4543
|
-
sourceType = envConfig.source;
|
|
4544
|
-
}
|
|
4545
|
-
if (!sourceType) {
|
|
4546
|
-
const projectConfig = await findProjectConfig();
|
|
4547
|
-
sourceType = projectConfig?.defaultSource;
|
|
4548
|
-
}
|
|
4549
|
-
if (!sourceType) {
|
|
4550
|
-
try {
|
|
4551
|
-
const detectionResult = await sourceDetector.detectSources({
|
|
4552
|
-
allowInteractive: true,
|
|
4553
|
-
respectPriority: true,
|
|
4554
|
-
showAuthStatus: true
|
|
4555
|
-
});
|
|
4556
|
-
sourceType = detectionResult.recommended;
|
|
4557
|
-
if (detectionResult.detected.length > 1) {
|
|
4558
|
-
logger.debug(
|
|
4559
|
-
`Detected sources: ${detectionResult.detected.map((s) => s.type).join(", ")}`
|
|
4560
|
-
);
|
|
4561
|
-
}
|
|
4562
|
-
if (!sourceType && detectionResult.detected.length === 0) {
|
|
4563
|
-
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "No supported sources detected");
|
|
4564
|
-
logger.log("");
|
|
4565
|
-
logger.info("Make sure you are in a git repository with a remote URL");
|
|
4566
|
-
logger.info("Or use --source flag for non-git sources:");
|
|
4567
|
-
logger.info(` ${theme.command("bragduck sync --source jira")}`);
|
|
4568
|
-
logger.info(` ${theme.command("bragduck sync --source confluence")}`);
|
|
4569
|
-
return;
|
|
4570
|
-
}
|
|
4571
|
-
} catch (error) {
|
|
4572
|
-
if (error instanceof GitError) {
|
|
4573
|
-
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "Not a git repository");
|
|
4574
|
-
logger.log("");
|
|
4575
|
-
logger.info("For non-git sources, use --source flag:");
|
|
4576
|
-
logger.info(` ${theme.command("bragduck sync --source jira")}`);
|
|
4577
|
-
logger.info(` ${theme.command("bragduck sync --source confluence")}`);
|
|
4578
|
-
logger.log("");
|
|
4579
|
-
logger.info("Or set default source in config:");
|
|
4580
|
-
logger.info(` ${theme.command("bragduck config set defaultSource jira")}`);
|
|
4581
|
-
return;
|
|
4582
|
-
}
|
|
4583
|
-
throw error;
|
|
4584
|
-
}
|
|
4585
|
-
}
|
|
4586
|
-
if (!sourceType || !AdapterFactory.isSupported(sourceType)) {
|
|
4587
|
-
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "Could not determine source");
|
|
4588
|
-
try {
|
|
4589
|
-
const detected = await sourceDetector.detectSources();
|
|
4590
|
-
if (detected.detected.length > 0) {
|
|
4591
|
-
logger.log("");
|
|
4592
|
-
logger.info("Detected sources:");
|
|
4593
|
-
for (const source of detected.detected) {
|
|
4594
|
-
const authStatus2 = source.isAuthenticated ? "\u2713 authenticated" : "\u2717 not authenticated";
|
|
4595
|
-
const repo = source.owner && source.repo ? `${source.owner}/${source.repo}` : source.host || "configured";
|
|
4596
|
-
logger.info(` \u2022 ${source.type} (${authStatus2}) - ${repo}`);
|
|
4597
|
-
}
|
|
4598
|
-
logger.log("");
|
|
4599
|
-
}
|
|
4600
|
-
} catch {
|
|
4601
|
-
}
|
|
4602
|
-
logger.info("Specify source explicitly:");
|
|
4603
|
-
logger.info(` ${theme.command("bragduck sync --source <type>")}`);
|
|
4604
|
-
logger.log("");
|
|
4605
|
-
logger.info("Supported sources: github, gitlab, bitbucket, jira, confluence");
|
|
4606
|
-
return;
|
|
4607
|
-
}
|
|
4608
|
-
if (sourceType === "jira" || sourceType === "confluence") {
|
|
4609
|
-
const creds = await storageService.getServiceCredentials(sourceType);
|
|
4610
|
-
const envInstance = loadEnvConfig()[`${sourceType}Instance`];
|
|
4611
|
-
const projectConfig = await findProjectConfig();
|
|
4612
|
-
const configInstance = projectConfig?.[`${sourceType}Instance`];
|
|
4613
|
-
if (!creds?.instanceUrl && !envInstance && !configInstance) {
|
|
4614
|
-
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `No ${sourceType} instance configured`);
|
|
4663
|
+
let selectedSource;
|
|
4664
|
+
if (options.source) {
|
|
4665
|
+
sourceType = options.source;
|
|
4666
|
+
if (!AdapterFactory.isSupported(sourceType)) {
|
|
4615
4667
|
logger.log("");
|
|
4616
|
-
logger.
|
|
4617
|
-
logger.
|
|
4618
|
-
logger.info(
|
|
4619
|
-
` ${theme.command(`bragduck config set ${sourceType}Instance company.atlassian.net`)}`
|
|
4620
|
-
);
|
|
4621
|
-
logger.info(
|
|
4622
|
-
` ${theme.command(`export BRAGDUCK_${sourceType.toUpperCase()}_INSTANCE=company.atlassian.net`)}`
|
|
4623
|
-
);
|
|
4668
|
+
logger.error(`Unsupported source: ${options.source}`);
|
|
4669
|
+
logger.log("");
|
|
4670
|
+
logger.info("Supported sources: github, gitlab, bitbucket, atlassian, jira, confluence");
|
|
4624
4671
|
return;
|
|
4625
4672
|
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
logger.log("");
|
|
4629
|
-
const adapter = AdapterFactory.getAdapter(sourceType);
|
|
4630
|
-
const repoSpinner = createStepSpinner(2, TOTAL_STEPS, "Validating repository");
|
|
4631
|
-
repoSpinner.start();
|
|
4632
|
-
await adapter.validate();
|
|
4633
|
-
const repoInfo = await adapter.getRepositoryInfo();
|
|
4634
|
-
succeedStepSpinner(
|
|
4635
|
-
repoSpinner,
|
|
4636
|
-
2,
|
|
4637
|
-
TOTAL_STEPS,
|
|
4638
|
-
`Repository: ${theme.value(repoInfo.fullName)}`
|
|
4639
|
-
);
|
|
4640
|
-
logger.log("");
|
|
4641
|
-
let days = options.days;
|
|
4642
|
-
if (!days) {
|
|
4643
|
-
const defaultDays = storageService.getConfig("defaultCommitDays");
|
|
4644
|
-
days = await promptDaysToScan(defaultDays);
|
|
4645
|
-
logger.log("");
|
|
4646
|
-
}
|
|
4647
|
-
const fetchSpinner = createStepSpinner(
|
|
4648
|
-
3,
|
|
4649
|
-
TOTAL_STEPS,
|
|
4650
|
-
`Fetching work items from the last ${days} days`
|
|
4651
|
-
);
|
|
4652
|
-
fetchSpinner.start();
|
|
4653
|
-
const workItems = await adapter.fetchWorkItems({
|
|
4654
|
-
days,
|
|
4655
|
-
author: options.all ? void 0 : await adapter.getCurrentUser() || void 0
|
|
4656
|
-
});
|
|
4657
|
-
if (workItems.length === 0) {
|
|
4658
|
-
failStepSpinner(fetchSpinner, 3, TOTAL_STEPS, `No work items found in the last ${days} days`);
|
|
4659
|
-
logger.log("");
|
|
4660
|
-
logger.info("Try increasing the number of days or check your activity");
|
|
4661
|
-
return;
|
|
4662
|
-
}
|
|
4663
|
-
succeedStepSpinner(
|
|
4664
|
-
fetchSpinner,
|
|
4665
|
-
3,
|
|
4666
|
-
TOTAL_STEPS,
|
|
4667
|
-
`Found ${theme.count(workItems.length)} work item${workItems.length > 1 ? "s" : ""}`
|
|
4668
|
-
);
|
|
4669
|
-
logger.log("");
|
|
4670
|
-
logger.log(formatCommitStats(workItems));
|
|
4671
|
-
logger.log("");
|
|
4672
|
-
let sortedCommits = [...workItems];
|
|
4673
|
-
if (workItems.length > 1) {
|
|
4674
|
-
const sortOption = await promptSortOption();
|
|
4675
|
-
logger.log("");
|
|
4676
|
-
if (sortOption === "date") {
|
|
4677
|
-
sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
4678
|
-
} else if (sortOption === "size") {
|
|
4679
|
-
sortedCommits.sort((a, b) => {
|
|
4680
|
-
const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
|
|
4681
|
-
const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
|
|
4682
|
-
return sizeB - sizeA;
|
|
4683
|
-
});
|
|
4684
|
-
} else if (sortOption === "files") {
|
|
4685
|
-
sortedCommits.sort((a, b) => {
|
|
4686
|
-
const filesA = a.diffStats?.filesChanged || 0;
|
|
4687
|
-
const filesB = b.diffStats?.filesChanged || 0;
|
|
4688
|
-
return filesB - filesA;
|
|
4689
|
-
});
|
|
4690
|
-
}
|
|
4691
|
-
}
|
|
4692
|
-
const selectedShas = await promptSelectCommits(sortedCommits);
|
|
4693
|
-
if (selectedShas.length === 0) {
|
|
4694
|
-
logger.log("");
|
|
4695
|
-
logger.info(theme.secondary("No work items selected. Sync cancelled."));
|
|
4696
|
-
logger.log("");
|
|
4697
|
-
return;
|
|
4698
|
-
}
|
|
4699
|
-
const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
|
|
4700
|
-
logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
|
|
4701
|
-
logger.log("");
|
|
4702
|
-
const existingBrags = await apiService.listBrags({ limit: 100 });
|
|
4703
|
-
logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
|
|
4704
|
-
const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
|
|
4705
|
-
logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
|
|
4706
|
-
const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
|
|
4707
|
-
const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
|
|
4708
|
-
logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
|
|
4709
|
-
if (duplicates.length > 0) {
|
|
4710
|
-
logger.log("");
|
|
4711
|
-
logger.info(
|
|
4712
|
-
colors.warning(
|
|
4713
|
-
`${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
|
|
4714
|
-
)
|
|
4715
|
-
);
|
|
4716
|
-
logger.log("");
|
|
4717
|
-
}
|
|
4718
|
-
if (newCommits.length === 0) {
|
|
4719
|
-
logger.log("");
|
|
4720
|
-
logger.info(
|
|
4721
|
-
theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
|
|
4722
|
-
);
|
|
4723
|
-
logger.log("");
|
|
4724
|
-
return;
|
|
4725
|
-
}
|
|
4726
|
-
const refineSpinner = createStepSpinner(
|
|
4727
|
-
4,
|
|
4728
|
-
TOTAL_STEPS,
|
|
4729
|
-
`Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
|
|
4730
|
-
);
|
|
4731
|
-
refineSpinner.start();
|
|
4732
|
-
const refineRequest = {
|
|
4733
|
-
brags: newCommits.map((c) => ({
|
|
4734
|
-
text: c.message,
|
|
4735
|
-
date: c.date,
|
|
4736
|
-
title: c.message.split("\n")[0]
|
|
4737
|
-
}))
|
|
4738
|
-
};
|
|
4739
|
-
const refineResponse = await apiService.refineBrags(refineRequest);
|
|
4740
|
-
let refinedBrags = refineResponse.refined_brags;
|
|
4741
|
-
succeedStepSpinner(refineSpinner, 4, TOTAL_STEPS, "Work items refined successfully");
|
|
4742
|
-
logger.log("");
|
|
4743
|
-
logger.info("Preview of refined brags:");
|
|
4744
|
-
logger.log("");
|
|
4745
|
-
logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
|
|
4746
|
-
logger.log("");
|
|
4747
|
-
const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
|
|
4748
|
-
if (acceptedBrags.length === 0) {
|
|
4673
|
+
selectedSource = sourceType;
|
|
4674
|
+
} else {
|
|
4749
4675
|
logger.log("");
|
|
4750
|
-
|
|
4676
|
+
selectedSource = await promptSelectService();
|
|
4751
4677
|
logger.log("");
|
|
4752
|
-
return;
|
|
4753
4678
|
}
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4679
|
+
if (selectedSource === "all") {
|
|
4680
|
+
await syncAllAuthenticatedServices(options);
|
|
4681
|
+
} else {
|
|
4682
|
+
const detectionSpinner = createStepSpinner(1, TOTAL_STEPS, "Preparing sync");
|
|
4683
|
+
detectionSpinner.start();
|
|
4684
|
+
sourceType = selectedSource;
|
|
4685
|
+
if (sourceType === "jira" || sourceType === "confluence") {
|
|
4686
|
+
const creds = await storageService.getServiceCredentials(sourceType);
|
|
4687
|
+
const envInstance = loadEnvConfig()[`${sourceType}Instance`];
|
|
4688
|
+
const projectConfig = await findProjectConfig();
|
|
4689
|
+
const configInstance = projectConfig?.[`${sourceType}Instance`];
|
|
4690
|
+
if (!creds?.instanceUrl && !envInstance && !configInstance) {
|
|
4691
|
+
failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `No ${sourceType} instance configured`);
|
|
4762
4692
|
logger.log("");
|
|
4693
|
+
logger.info("Configure instance via:");
|
|
4694
|
+
logger.info(` ${theme.command("bragduck auth atlassian")} (interactive)`);
|
|
4695
|
+
logger.info(
|
|
4696
|
+
` ${theme.command(`bragduck config set ${sourceType}Instance company.atlassian.net`)}`
|
|
4697
|
+
);
|
|
4698
|
+
logger.info(
|
|
4699
|
+
` ${theme.command(`export BRAGDUCK_${sourceType.toUpperCase()}_INSTANCE=company.atlassian.net`)}`
|
|
4700
|
+
);
|
|
4701
|
+
return;
|
|
4763
4702
|
}
|
|
4764
|
-
}
|
|
4765
|
-
|
|
4703
|
+
}
|
|
4704
|
+
succeedStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source: ${theme.value(sourceType)}`);
|
|
4705
|
+
logger.log("");
|
|
4706
|
+
const result = await syncSingleService(sourceType, options, TOTAL_STEPS);
|
|
4707
|
+
if (result.created > 0) {
|
|
4708
|
+
logger.log(boxen6(formatSuccessMessage(result.created), boxStyles.success));
|
|
4766
4709
|
}
|
|
4767
4710
|
}
|
|
4768
|
-
const createSpinner2 = createStepSpinner(
|
|
4769
|
-
5,
|
|
4770
|
-
TOTAL_STEPS,
|
|
4771
|
-
`Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
|
|
4772
|
-
);
|
|
4773
|
-
createSpinner2.start();
|
|
4774
|
-
const createRequest = {
|
|
4775
|
-
brags: acceptedBrags.map((refined, index) => {
|
|
4776
|
-
const originalCommit = newCommits[index];
|
|
4777
|
-
return {
|
|
4778
|
-
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
4779
|
-
title: refined.refined_title,
|
|
4780
|
-
description: refined.refined_description,
|
|
4781
|
-
tags: refined.suggested_tags,
|
|
4782
|
-
repository: repoInfo.url,
|
|
4783
|
-
date: originalCommit?.date || "",
|
|
4784
|
-
commit_url: originalCommit?.url || "",
|
|
4785
|
-
impact_score: refined.suggested_impactLevel,
|
|
4786
|
-
impact_description: refined.impact_description,
|
|
4787
|
-
attachments: originalCommit?.url ? [originalCommit.url] : [],
|
|
4788
|
-
orgId: selectedOrgId || void 0,
|
|
4789
|
-
// External fields for non-git sources (Jira, Confluence, etc.)
|
|
4790
|
-
externalId: originalCommit?.externalId,
|
|
4791
|
-
externalType: originalCommit?.externalType,
|
|
4792
|
-
externalSource: originalCommit?.externalSource,
|
|
4793
|
-
externalUrl: originalCommit?.externalUrl
|
|
4794
|
-
};
|
|
4795
|
-
})
|
|
4796
|
-
};
|
|
4797
|
-
const createResponse = await apiService.createBrags(createRequest);
|
|
4798
|
-
succeedStepSpinner(
|
|
4799
|
-
createSpinner2,
|
|
4800
|
-
5,
|
|
4801
|
-
TOTAL_STEPS,
|
|
4802
|
-
`Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
|
|
4803
|
-
);
|
|
4804
|
-
logger.log("");
|
|
4805
|
-
logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
|
|
4806
4711
|
} catch (error) {
|
|
4807
4712
|
if (error instanceof CancelPromptError) {
|
|
4808
4713
|
logger.log("");
|
|
@@ -4812,11 +4717,13 @@ async function syncCommand(options = {}) {
|
|
|
4812
4717
|
}
|
|
4813
4718
|
const err = error;
|
|
4814
4719
|
logger.log("");
|
|
4815
|
-
logger.log(
|
|
4720
|
+
logger.log(
|
|
4721
|
+
boxen6(formatErrorMessage(err.message, getErrorHint2(err, sourceType)), boxStyles.error)
|
|
4722
|
+
);
|
|
4816
4723
|
process.exit(1);
|
|
4817
4724
|
}
|
|
4818
4725
|
}
|
|
4819
|
-
function getErrorHint2(error) {
|
|
4726
|
+
function getErrorHint2(error, sourceType) {
|
|
4820
4727
|
if (error.name === "GitHubError") {
|
|
4821
4728
|
return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
|
|
4822
4729
|
}
|
|
@@ -4824,6 +4731,9 @@ function getErrorHint2(error) {
|
|
|
4824
4731
|
return "Make sure you are in a git repository";
|
|
4825
4732
|
}
|
|
4826
4733
|
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
4734
|
+
if (sourceType === "jira" || sourceType === "confluence") {
|
|
4735
|
+
return 'This is your Bragduck platform session. Run "bragduck auth login" (not "bragduck auth atlassian")';
|
|
4736
|
+
}
|
|
4827
4737
|
return 'Run "bragduck auth login" to login again';
|
|
4828
4738
|
}
|
|
4829
4739
|
if (error.name === "NetworkError") {
|