@bragduck/cli 2.16.3 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bragduck.js +220 -21
- package/dist/bin/bragduck.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -2633,10 +2633,189 @@ var CancelPromptError = class extends Error {
|
|
|
2633
2633
|
init_api_service();
|
|
2634
2634
|
init_storage_service();
|
|
2635
2635
|
init_auth_service();
|
|
2636
|
+
import { select as select3 } from "@inquirer/prompts";
|
|
2637
|
+
import boxen6 from "boxen";
|
|
2638
|
+
|
|
2639
|
+
// src/utils/source-detector.ts
|
|
2640
|
+
init_esm_shims();
|
|
2641
|
+
init_errors();
|
|
2642
|
+
init_storage_service();
|
|
2643
|
+
import { exec as exec3 } from "child_process";
|
|
2644
|
+
import { promisify as promisify3 } from "util";
|
|
2645
|
+
import { select } from "@inquirer/prompts";
|
|
2646
|
+
var execAsync3 = promisify3(exec3);
|
|
2647
|
+
var SourceDetector = class {
|
|
2648
|
+
/**
|
|
2649
|
+
* Detect all possible sources from git remotes
|
|
2650
|
+
*/
|
|
2651
|
+
async detectSources(options = {}) {
|
|
2652
|
+
const detected = [];
|
|
2653
|
+
try {
|
|
2654
|
+
const { stdout } = await execAsync3("git remote -v");
|
|
2655
|
+
const remotes = this.parseRemotes(stdout);
|
|
2656
|
+
for (const remote of remotes) {
|
|
2657
|
+
const source = this.parseRemoteUrl(remote.url);
|
|
2658
|
+
if (source) {
|
|
2659
|
+
const isAuthenticated = await this.checkAuthentication(source.type);
|
|
2660
|
+
detected.push({
|
|
2661
|
+
...source,
|
|
2662
|
+
remoteUrl: remote.url,
|
|
2663
|
+
isAuthenticated
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
} catch {
|
|
2668
|
+
throw new GitError("Not a git repository");
|
|
2669
|
+
}
|
|
2670
|
+
let recommended;
|
|
2671
|
+
if (detected.length > 1 && options.allowInteractive && process.stdout.isTTY) {
|
|
2672
|
+
try {
|
|
2673
|
+
recommended = await this.promptSourceSelection(detected, options.showAuthStatus);
|
|
2674
|
+
} catch {
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
if (!recommended && options.respectPriority) {
|
|
2678
|
+
const priority = await storageService.getConfigWithHierarchy("sourcePriority");
|
|
2679
|
+
if (priority) {
|
|
2680
|
+
recommended = this.applyPriority(detected, priority);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
if (!recommended) {
|
|
2684
|
+
recommended = this.selectRecommendedSource(detected);
|
|
2685
|
+
}
|
|
2686
|
+
return { detected, recommended };
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Prompt user to select a source interactively
|
|
2690
|
+
*/
|
|
2691
|
+
async promptSourceSelection(sources, showAuthStatus = true) {
|
|
2692
|
+
const choices = sources.map((source) => {
|
|
2693
|
+
const authStatus2 = showAuthStatus ? source.isAuthenticated ? "\u2713 authenticated" : "\u2717 not authenticated" : "";
|
|
2694
|
+
const repo = source.owner && source.repo ? `${source.owner}/${source.repo}` : source.host;
|
|
2695
|
+
const name = `${source.type}${authStatus2 ? ` (${authStatus2})` : ""} - ${repo}`;
|
|
2696
|
+
return {
|
|
2697
|
+
name,
|
|
2698
|
+
value: source.type,
|
|
2699
|
+
description: source.remoteUrl
|
|
2700
|
+
};
|
|
2701
|
+
});
|
|
2702
|
+
return await select({
|
|
2703
|
+
message: "Multiple sources detected. Which do you want to sync?",
|
|
2704
|
+
choices
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
/**
|
|
2708
|
+
* Apply configured priority to select source
|
|
2709
|
+
*/
|
|
2710
|
+
applyPriority(sources, priority) {
|
|
2711
|
+
for (const sourceType of priority) {
|
|
2712
|
+
const found = sources.find((s) => s.type === sourceType);
|
|
2713
|
+
if (found) return found.type;
|
|
2714
|
+
}
|
|
2715
|
+
return void 0;
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Parse git remote -v output
|
|
2719
|
+
*/
|
|
2720
|
+
parseRemotes(output) {
|
|
2721
|
+
const lines = output.split("\n").filter(Boolean);
|
|
2722
|
+
const remotes = /* @__PURE__ */ new Map();
|
|
2723
|
+
for (const line of lines) {
|
|
2724
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
|
|
2725
|
+
if (match && match[1] && match[2]) {
|
|
2726
|
+
remotes.set(match[1], match[2]);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
return Array.from(remotes.entries()).map(([name, url]) => ({ name, url }));
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Parse remote URL to detect source type
|
|
2733
|
+
*/
|
|
2734
|
+
parseRemoteUrl(url) {
|
|
2735
|
+
if (url.includes("github.com")) {
|
|
2736
|
+
const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
2737
|
+
if (match && match[1] && match[2]) {
|
|
2738
|
+
return {
|
|
2739
|
+
type: "github",
|
|
2740
|
+
host: "github.com",
|
|
2741
|
+
owner: match[1],
|
|
2742
|
+
repo: match[2]
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
if (url.includes("gitlab.com")) {
|
|
2747
|
+
const match = url.match(/gitlab\.com[:/]([^/]+)\/([^/.]+)/);
|
|
2748
|
+
if (match && match[1] && match[2]) {
|
|
2749
|
+
return {
|
|
2750
|
+
type: "gitlab",
|
|
2751
|
+
host: "gitlab.com",
|
|
2752
|
+
owner: match[1],
|
|
2753
|
+
repo: match[2]
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (url.includes("bitbucket.org")) {
|
|
2758
|
+
const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
|
|
2759
|
+
if (match && match[1] && match[2]) {
|
|
2760
|
+
return {
|
|
2761
|
+
type: "bitbucket",
|
|
2762
|
+
host: "bitbucket.org",
|
|
2763
|
+
owner: match[1],
|
|
2764
|
+
repo: match[2]
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
if (url.match(/\/scm\/|bitbucket\./)) {
|
|
2769
|
+
const match = url.match(/([^/:]+)[:/]scm\/([^/]+)\/([^/.]+)/);
|
|
2770
|
+
if (match && match[1] && match[2] && match[3]) {
|
|
2771
|
+
return {
|
|
2772
|
+
type: "atlassian",
|
|
2773
|
+
host: match[1],
|
|
2774
|
+
owner: match[2],
|
|
2775
|
+
repo: match[3]
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
return null;
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Check if user is authenticated with a specific source
|
|
2783
|
+
*/
|
|
2784
|
+
async checkAuthentication(type) {
|
|
2785
|
+
try {
|
|
2786
|
+
if (type === "github") {
|
|
2787
|
+
await execAsync3("command gh auth status");
|
|
2788
|
+
return true;
|
|
2789
|
+
} else if (type === "bitbucket" || type === "atlassian") {
|
|
2790
|
+
return await storageService.isServiceAuthenticated("bitbucket");
|
|
2791
|
+
} else if (type === "gitlab") {
|
|
2792
|
+
return await storageService.isServiceAuthenticated("gitlab");
|
|
2793
|
+
} else if (type === "jira") {
|
|
2794
|
+
return await storageService.isServiceAuthenticated("jira");
|
|
2795
|
+
} else if (type === "confluence") {
|
|
2796
|
+
return await storageService.isServiceAuthenticated("confluence");
|
|
2797
|
+
}
|
|
2798
|
+
return false;
|
|
2799
|
+
} catch {
|
|
2800
|
+
return false;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Select recommended source (prefer authenticated GitHub)
|
|
2805
|
+
*/
|
|
2806
|
+
selectRecommendedSource(sources) {
|
|
2807
|
+
const authenticated = sources.find((s) => s.isAuthenticated);
|
|
2808
|
+
if (authenticated) return authenticated.type;
|
|
2809
|
+
const github = sources.find((s) => s.type === "github");
|
|
2810
|
+
if (github) return "github";
|
|
2811
|
+
return sources[0]?.type;
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
var sourceDetector = new SourceDetector();
|
|
2815
|
+
|
|
2816
|
+
// src/commands/sync.ts
|
|
2636
2817
|
init_env_loader();
|
|
2637
2818
|
init_config_loader();
|
|
2638
|
-
import { select as select2 } from "@inquirer/prompts";
|
|
2639
|
-
import boxen6 from "boxen";
|
|
2640
2819
|
|
|
2641
2820
|
// src/sync/adapter-factory.ts
|
|
2642
2821
|
init_esm_shims();
|
|
@@ -2695,9 +2874,9 @@ init_esm_shims();
|
|
|
2695
2874
|
init_errors();
|
|
2696
2875
|
init_logger();
|
|
2697
2876
|
init_storage_service();
|
|
2698
|
-
import { exec as
|
|
2699
|
-
import { promisify as
|
|
2700
|
-
var
|
|
2877
|
+
import { exec as exec4 } from "child_process";
|
|
2878
|
+
import { promisify as promisify4 } from "util";
|
|
2879
|
+
var execAsync4 = promisify4(exec4);
|
|
2701
2880
|
var BitbucketService = class {
|
|
2702
2881
|
BITBUCKET_API_BASE = "https://api.bitbucket.org/2.0";
|
|
2703
2882
|
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
@@ -2782,7 +2961,7 @@ var BitbucketService = class {
|
|
|
2782
2961
|
*/
|
|
2783
2962
|
async getRepoFromGit() {
|
|
2784
2963
|
try {
|
|
2785
|
-
const { stdout } = await
|
|
2964
|
+
const { stdout } = await execAsync4("command git remote get-url origin");
|
|
2786
2965
|
const remoteUrl = stdout.trim();
|
|
2787
2966
|
const parsed = this.parseRemoteUrl(remoteUrl);
|
|
2788
2967
|
if (!parsed) {
|
|
@@ -2977,10 +3156,10 @@ init_esm_shims();
|
|
|
2977
3156
|
init_errors();
|
|
2978
3157
|
init_logger();
|
|
2979
3158
|
init_storage_service();
|
|
2980
|
-
import { exec as
|
|
2981
|
-
import { promisify as
|
|
3159
|
+
import { exec as exec5 } from "child_process";
|
|
3160
|
+
import { promisify as promisify5 } from "util";
|
|
2982
3161
|
import { URLSearchParams as URLSearchParams3 } from "url";
|
|
2983
|
-
var
|
|
3162
|
+
var execAsync5 = promisify5(exec5);
|
|
2984
3163
|
var GitLabService = class {
|
|
2985
3164
|
DEFAULT_INSTANCE = "https://gitlab.com";
|
|
2986
3165
|
MAX_DESCRIPTION_LENGTH = 5e3;
|
|
@@ -3066,7 +3245,7 @@ var GitLabService = class {
|
|
|
3066
3245
|
*/
|
|
3067
3246
|
async getProjectFromGit() {
|
|
3068
3247
|
try {
|
|
3069
|
-
const { stdout } = await
|
|
3248
|
+
const { stdout } = await execAsync5("git remote get-url origin");
|
|
3070
3249
|
const remoteUrl = stdout.trim();
|
|
3071
3250
|
const parsed = this.parseRemoteUrl(remoteUrl);
|
|
3072
3251
|
if (!parsed) {
|
|
@@ -4299,7 +4478,7 @@ init_logger();
|
|
|
4299
4478
|
|
|
4300
4479
|
// src/ui/prompts.ts
|
|
4301
4480
|
init_esm_shims();
|
|
4302
|
-
import { checkbox, confirm, input as input2, select, editor } from "@inquirer/prompts";
|
|
4481
|
+
import { checkbox, confirm, input as input2, select as select2, editor } from "@inquirer/prompts";
|
|
4303
4482
|
import boxen4 from "boxen";
|
|
4304
4483
|
|
|
4305
4484
|
// src/ui/formatters.ts
|
|
@@ -4456,7 +4635,7 @@ async function promptDaysToScan(defaultDays = 30) {
|
|
|
4456
4635
|
{ name: "90 days", value: "90", description: "Last 3 months" },
|
|
4457
4636
|
{ name: "Custom", value: "custom", description: "Enter custom number of days" }
|
|
4458
4637
|
];
|
|
4459
|
-
const selected = await
|
|
4638
|
+
const selected = await select2({
|
|
4460
4639
|
message: "How many days back should we scan for PRs?",
|
|
4461
4640
|
choices,
|
|
4462
4641
|
default: "30"
|
|
@@ -4488,7 +4667,7 @@ async function promptSortOption() {
|
|
|
4488
4667
|
{ name: "By files (most files)", value: "files", description: "Most files changed" },
|
|
4489
4668
|
{ name: "No sorting", value: "none", description: "Keep original order" }
|
|
4490
4669
|
];
|
|
4491
|
-
return await
|
|
4670
|
+
return await select2({
|
|
4492
4671
|
message: "How would you like to sort the PRs?",
|
|
4493
4672
|
choices,
|
|
4494
4673
|
default: "date"
|
|
@@ -4502,7 +4681,7 @@ async function promptSelectOrganisation(organisations) {
|
|
|
4502
4681
|
value: org.id
|
|
4503
4682
|
}))
|
|
4504
4683
|
];
|
|
4505
|
-
const selected = await
|
|
4684
|
+
const selected = await select2({
|
|
4506
4685
|
message: "Attach brags to which company?",
|
|
4507
4686
|
choices,
|
|
4508
4687
|
default: "none"
|
|
@@ -4552,7 +4731,7 @@ ${theme.label("PR Link")} ${colors.link(prUrl)}`;
|
|
|
4552
4731
|
}
|
|
4553
4732
|
console.log(boxen4(bragDetails, boxStyles.info));
|
|
4554
4733
|
console.log("");
|
|
4555
|
-
const action = await
|
|
4734
|
+
const action = await select2({
|
|
4556
4735
|
message: `What would you like to do with this brag?`,
|
|
4557
4736
|
choices: [
|
|
4558
4737
|
{ name: "\u2713 Accept", value: "accept", description: "Add this brag as-is" },
|
|
@@ -4728,7 +4907,7 @@ async function promptSelectService() {
|
|
|
4728
4907
|
description: `Sync all ${authenticatedSyncServices.length} authenticated service${authenticatedSyncServices.length > 1 ? "s" : ""}`
|
|
4729
4908
|
});
|
|
4730
4909
|
}
|
|
4731
|
-
const selected = await
|
|
4910
|
+
const selected = await select3({
|
|
4732
4911
|
message: "Select a service to sync:",
|
|
4733
4912
|
choices: serviceChoices
|
|
4734
4913
|
});
|
|
@@ -4782,7 +4961,7 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
|
|
|
4782
4961
|
fetchSpinner.start();
|
|
4783
4962
|
const workItems = await adapter.fetchWorkItems({
|
|
4784
4963
|
days,
|
|
4785
|
-
author:
|
|
4964
|
+
author: await adapter.getCurrentUser() || void 0
|
|
4786
4965
|
});
|
|
4787
4966
|
if (workItems.length === 0) {
|
|
4788
4967
|
failStepSpinner(fetchSpinner, 3, TOTAL_STEPS, `No work items found in the last ${days} days`);
|
|
@@ -4979,16 +5158,32 @@ async function syncAllAuthenticatedServices(options) {
|
|
|
4979
5158
|
"confluence"
|
|
4980
5159
|
];
|
|
4981
5160
|
const authenticatedServices = await storageService.getAuthenticatedServices();
|
|
4982
|
-
|
|
5161
|
+
let servicesToSync = authenticatedServices.filter(
|
|
4983
5162
|
(service) => service !== "bragduck" && allServices.includes(service)
|
|
4984
5163
|
);
|
|
5164
|
+
try {
|
|
5165
|
+
const { detected } = await sourceDetector.detectSources();
|
|
5166
|
+
logger.debug(`Detected ${detected.length} git source(s) from repository`);
|
|
5167
|
+
for (const source of detected) {
|
|
5168
|
+
if (["github", "gitlab", "bitbucket", "atlassian"].includes(source.type)) {
|
|
5169
|
+
if (!servicesToSync.includes(source.type)) {
|
|
5170
|
+
servicesToSync.push(source.type);
|
|
5171
|
+
logger.debug(`Added local ${source.type} repo to sync list`);
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
} catch {
|
|
5176
|
+
logger.debug("No local git repository detected");
|
|
5177
|
+
}
|
|
4985
5178
|
if (servicesToSync.length === 0) {
|
|
4986
5179
|
logger.log("");
|
|
4987
|
-
logger.error("No authenticated services
|
|
5180
|
+
logger.error("No services to sync (no authenticated services and no local git repository).");
|
|
4988
5181
|
logger.log("");
|
|
4989
5182
|
logger.info("Authenticate a service first:");
|
|
4990
5183
|
logger.info(` ${theme.command("bragduck auth github")}`);
|
|
4991
5184
|
logger.info(` ${theme.command("bragduck auth atlassian")}`);
|
|
5185
|
+
logger.log("");
|
|
5186
|
+
logger.info("Or run this command from within a git repository.");
|
|
4992
5187
|
return;
|
|
4993
5188
|
}
|
|
4994
5189
|
logger.info(
|
|
@@ -5058,6 +5253,7 @@ async function syncAllAuthenticatedServices(options) {
|
|
|
5058
5253
|
)
|
|
5059
5254
|
);
|
|
5060
5255
|
}
|
|
5256
|
+
logger.debug("Sync complete, exiting...");
|
|
5061
5257
|
}
|
|
5062
5258
|
async function syncCommand(options = {}) {
|
|
5063
5259
|
logger.log("");
|
|
@@ -5089,7 +5285,10 @@ async function syncCommand(options = {}) {
|
|
|
5089
5285
|
}
|
|
5090
5286
|
logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with sync`);
|
|
5091
5287
|
let selectedSource;
|
|
5092
|
-
if (options.
|
|
5288
|
+
if (options.all) {
|
|
5289
|
+
selectedSource = "all";
|
|
5290
|
+
logger.debug("Using --all flag: syncing all authenticated services");
|
|
5291
|
+
} else if (options.source) {
|
|
5093
5292
|
sourceType = options.source;
|
|
5094
5293
|
if (!AdapterFactory.isSupported(sourceType)) {
|
|
5095
5294
|
logger.log("");
|
|
@@ -5915,7 +6114,7 @@ program.command("auth [subcommand]").description("Manage authentication (subcomm
|
|
|
5915
6114
|
process.exit(1);
|
|
5916
6115
|
}
|
|
5917
6116
|
});
|
|
5918
|
-
program.command("sync").description("Sync work items and create brags").option("-d, --days <number>", "Number of days to scan", (val) => parseInt(val, 10)).option("-t, --today", "Scan the last 24 hours (shorthand for --days 1)").option("--turbo", "Turbo mode: skip all prompts, auto-select all items, auto-accept refinements").option("-a, --all", "
|
|
6117
|
+
program.command("sync").description("Sync work items and create brags").option("-d, --days <number>", "Number of days to scan", (val) => parseInt(val, 10)).option("-t, --today", "Scan the last 24 hours (shorthand for --days 1)").option("--turbo", "Turbo mode: skip all prompts, auto-select all items, auto-accept refinements").option("-a, --all", "Sync all authenticated services (skip service selection)").option("-s, --source <type>", "Explicit source type (github)").action(async (options) => {
|
|
5919
6118
|
try {
|
|
5920
6119
|
await syncCommand(options);
|
|
5921
6120
|
} catch (error) {
|