@bragduck/cli 2.17.0 → 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.
@@ -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 exec3 } from "child_process";
2699
- import { promisify as promisify3 } from "util";
2700
- var execAsync3 = promisify3(exec3);
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 execAsync3("command git remote get-url origin");
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 exec4 } from "child_process";
2981
- import { promisify as promisify4 } from "util";
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 execAsync4 = promisify4(exec4);
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 execAsync4("git remote get-url origin");
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 select({
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 select({
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 select({
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 select({
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 select2({
4910
+ const selected = await select3({
4732
4911
  message: "Select a service to sync:",
4733
4912
  choices: serviceChoices
4734
4913
  });
@@ -4979,16 +5158,32 @@ async function syncAllAuthenticatedServices(options) {
4979
5158
  "confluence"
4980
5159
  ];
4981
5160
  const authenticatedServices = await storageService.getAuthenticatedServices();
4982
- const servicesToSync = authenticatedServices.filter(
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 found.");
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("");