@pilatos/bitbucket-cli 1.16.1 → 1.17.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/index.js CHANGED
@@ -18495,6 +18495,54 @@ var chalk = createChalk();
18495
18495
  var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
18496
18496
  var source_default = chalk;
18497
18497
 
18498
+ // src/services/locale.ts
18499
+ var DEFAULT_LOCALE = "en-US";
18500
+ function normalizePosixLocale(value) {
18501
+ const trimmed = value.trim();
18502
+ if (trimmed.length === 0) {
18503
+ return;
18504
+ }
18505
+ const base = trimmed.split(/[.@]/, 1)[0];
18506
+ if (base.length === 0) {
18507
+ return;
18508
+ }
18509
+ const upper = base.toUpperCase();
18510
+ if (upper === "C" || upper === "POSIX") {
18511
+ return DEFAULT_LOCALE;
18512
+ }
18513
+ return base.replace(/_/g, "-");
18514
+ }
18515
+ function detectSystemLocale(env2 = process.env) {
18516
+ const candidates = [env2.LC_TIME, env2.LC_ALL, env2.LANG];
18517
+ for (const candidate of candidates) {
18518
+ if (typeof candidate !== "string") {
18519
+ continue;
18520
+ }
18521
+ const normalized = normalizePosixLocale(candidate);
18522
+ if (normalized) {
18523
+ return normalized;
18524
+ }
18525
+ }
18526
+ return DEFAULT_LOCALE;
18527
+ }
18528
+ function resolveLocale(options) {
18529
+ const env2 = options.env ?? process.env;
18530
+ if (typeof options.explicit === "string") {
18531
+ const trimmed = options.explicit.trim();
18532
+ if (trimmed.length > 0) {
18533
+ return trimmed;
18534
+ }
18535
+ }
18536
+ const fromEnvVar = env2.BB_LOCALE;
18537
+ if (typeof fromEnvVar === "string") {
18538
+ const trimmed = fromEnvVar.trim();
18539
+ if (trimmed.length > 0) {
18540
+ return trimmed;
18541
+ }
18542
+ }
18543
+ return detectSystemLocale(env2);
18544
+ }
18545
+
18498
18546
  // src/services/output.project.ts
18499
18547
  function projectFields(item, fields) {
18500
18548
  if (item === null || typeof item !== "object") {
@@ -18521,7 +18569,143 @@ function deepGet(source, path) {
18521
18569
  return current === undefined ? null : current;
18522
18570
  }
18523
18571
 
18572
+ // src/services/spinner.ts
18573
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
18574
+ var FRAME_INTERVAL_MS = 80;
18575
+ var ESC = "\x1B";
18576
+ var CLEAR_LINE = `\r${ESC}[2K`;
18577
+ var HIDE_CURSOR = `${ESC}[?25l`;
18578
+ var SHOW_CURSOR = `${ESC}[?25h`;
18579
+ var COLOR_CYAN = `${ESC}[36m`;
18580
+ var COLOR_GREEN = `${ESC}[32m`;
18581
+ var COLOR_RED = `${ESC}[31m`;
18582
+ var COLOR_RESET = `${ESC}[0m`;
18583
+
18584
+ class Spinner {
18585
+ text;
18586
+ enabled;
18587
+ noColor;
18588
+ stream;
18589
+ onStop;
18590
+ timer = null;
18591
+ frameIndex = 0;
18592
+ active = false;
18593
+ exitHandler = null;
18594
+ constructor(text, options) {
18595
+ this.text = text;
18596
+ this.enabled = options.enabled;
18597
+ this.noColor = options.noColor;
18598
+ this.stream = options.stream;
18599
+ this.onStop = options.onStop;
18600
+ }
18601
+ start() {
18602
+ if (!this.enabled || this.active) {
18603
+ return this;
18604
+ }
18605
+ this.active = true;
18606
+ this.stream.write(HIDE_CURSOR);
18607
+ this.render();
18608
+ this.timer = setInterval(() => {
18609
+ this.frameIndex = (this.frameIndex + 1) % FRAMES.length;
18610
+ this.render();
18611
+ }, FRAME_INTERVAL_MS);
18612
+ this.timer.unref?.();
18613
+ this.exitHandler = () => {
18614
+ if (this.active) {
18615
+ this.active = false;
18616
+ if (this.timer) {
18617
+ clearInterval(this.timer);
18618
+ this.timer = null;
18619
+ }
18620
+ this.stream.write(`${CLEAR_LINE}${SHOW_CURSOR}`);
18621
+ }
18622
+ };
18623
+ process.once("SIGINT", this.exitHandler);
18624
+ process.once("SIGTERM", this.exitHandler);
18625
+ process.once("exit", this.exitHandler);
18626
+ return this;
18627
+ }
18628
+ stop() {
18629
+ if (!this.active) {
18630
+ return this.detach();
18631
+ }
18632
+ this.active = false;
18633
+ if (this.timer) {
18634
+ clearInterval(this.timer);
18635
+ this.timer = null;
18636
+ }
18637
+ this.stream.write(`${CLEAR_LINE}${SHOW_CURSOR}`);
18638
+ return this.detach();
18639
+ }
18640
+ succeed(message) {
18641
+ this.stop();
18642
+ if (this.enabled && message) {
18643
+ const symbol = this.colorize("\u2713", COLOR_GREEN);
18644
+ this.stream.write(`${symbol} ${message}
18645
+ `);
18646
+ }
18647
+ return this;
18648
+ }
18649
+ fail(message) {
18650
+ this.stop();
18651
+ if (this.enabled && message) {
18652
+ const symbol = this.colorize("\u2717", COLOR_RED);
18653
+ this.stream.write(`${symbol} ${message}
18654
+ `);
18655
+ }
18656
+ return this;
18657
+ }
18658
+ setText(text) {
18659
+ this.text = text;
18660
+ if (this.active) {
18661
+ this.render();
18662
+ }
18663
+ return this;
18664
+ }
18665
+ render() {
18666
+ if (!this.active)
18667
+ return;
18668
+ const frame = FRAMES[this.frameIndex] ?? FRAMES[0];
18669
+ const symbol = this.colorize(frame, COLOR_CYAN);
18670
+ this.stream.write(`${CLEAR_LINE}${symbol} ${this.text}`);
18671
+ }
18672
+ colorize(text, color) {
18673
+ return this.noColor ? text : `${color}${text}${COLOR_RESET}`;
18674
+ }
18675
+ detach() {
18676
+ if (this.exitHandler) {
18677
+ process.removeListener("SIGINT", this.exitHandler);
18678
+ process.removeListener("SIGTERM", this.exitHandler);
18679
+ process.removeListener("exit", this.exitHandler);
18680
+ this.exitHandler = null;
18681
+ }
18682
+ if (this.onStop) {
18683
+ const cb = this.onStop;
18684
+ this.onStop = undefined;
18685
+ cb();
18686
+ }
18687
+ return this;
18688
+ }
18689
+ }
18690
+ function createNoopSpinner() {
18691
+ const noop = {
18692
+ start: () => noop,
18693
+ stop: () => noop,
18694
+ succeed: () => noop,
18695
+ fail: () => noop,
18696
+ setText: () => noop
18697
+ };
18698
+ return noop;
18699
+ }
18700
+
18524
18701
  // src/services/output.service.ts
18702
+ var DATE_FORMAT_OPTIONS = {
18703
+ year: "numeric",
18704
+ month: "short",
18705
+ day: "numeric",
18706
+ hour: "2-digit",
18707
+ minute: "2-digit"
18708
+ };
18525
18709
  var CONTROL_CHARS = /(\x1b\[[0-9;?]*m)|\x1b\[[0-9;?]*[A-Za-ln-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x9B\x9D]/g;
18526
18710
  function stripControl(value) {
18527
18711
  return value.replace(CONTROL_CHARS, (_match, sgr) => sgr ?? "");
@@ -18540,14 +18724,42 @@ var WRAPPER_ARRAY_KEYS = [
18540
18724
 
18541
18725
  class OutputService {
18542
18726
  noColor;
18727
+ noUnicode;
18728
+ locale;
18543
18729
  jsonFormatOptions = {};
18730
+ activeSpinner = null;
18544
18731
  constructor(options) {
18545
18732
  this.noColor = options?.noColor ?? false;
18733
+ this.noUnicode = options?.noUnicode ?? false;
18734
+ this.locale = options?.locale ?? DEFAULT_LOCALE;
18546
18735
  }
18547
18736
  setJsonFormatOptions(options) {
18548
18737
  this.jsonFormatOptions = { ...options };
18549
18738
  }
18739
+ isJsonMode() {
18740
+ return this.jsonFormatOptions.json === true;
18741
+ }
18742
+ spinner(text) {
18743
+ const enabled = !this.isJsonMode() && !!process.stdout.isTTY && true;
18744
+ if (!enabled) {
18745
+ return createNoopSpinner();
18746
+ }
18747
+ this.activeSpinner?.stop();
18748
+ const spinner = new Spinner(text, {
18749
+ enabled: true,
18750
+ noColor: this.noColor,
18751
+ stream: process.stdout,
18752
+ onStop: () => {
18753
+ if (this.activeSpinner === spinner) {
18754
+ this.activeSpinner = null;
18755
+ }
18756
+ }
18757
+ });
18758
+ this.activeSpinner = spinner;
18759
+ return spinner;
18760
+ }
18550
18761
  async json(data) {
18762
+ this.stopActiveSpinner();
18551
18763
  const { fields, jq } = this.jsonFormatOptions;
18552
18764
  let result = data;
18553
18765
  if (fields && fields.length > 0) {
@@ -18565,9 +18777,11 @@ class OutputService {
18565
18777
  console.log(JSON.stringify(result, null, 2));
18566
18778
  }
18567
18779
  jsonError(data) {
18780
+ this.stopActiveSpinner();
18568
18781
  console.error(JSON.stringify(data));
18569
18782
  }
18570
18783
  table(headers, rows) {
18784
+ this.stopActiveSpinner();
18571
18785
  if (rows.length === 0) {
18572
18786
  return;
18573
18787
  }
@@ -18586,24 +18800,47 @@ class OutputService {
18586
18800
  }
18587
18801
  }
18588
18802
  success(message) {
18589
- const symbol = this.format("\u2713", source_default.green);
18803
+ this.stopActiveSpinner();
18804
+ const symbol = this.format(this.symbol("\u2713", "OK"), source_default.green);
18590
18805
  console.log(`${symbol} ${stripControl(message)}`);
18591
18806
  }
18592
18807
  error(message) {
18593
- const symbol = this.format("\u2717", source_default.red);
18808
+ this.stopActiveSpinner();
18809
+ const symbol = this.format(this.symbol("\u2717", "ERR"), source_default.red);
18594
18810
  console.error(`${symbol} ${stripControl(message)}`);
18595
18811
  }
18596
18812
  warning(message) {
18597
- const symbol = this.format("\u26A0", source_default.yellow);
18813
+ this.stopActiveSpinner();
18814
+ const symbol = this.format(this.symbol("\u26A0", "!!"), source_default.yellow);
18598
18815
  console.warn(`${symbol} ${stripControl(message)}`);
18599
18816
  }
18600
18817
  info(message) {
18601
- const symbol = this.format("\u2139", source_default.blue);
18818
+ this.stopActiveSpinner();
18819
+ const symbol = this.format(this.symbol("\u2139", "i"), source_default.blue);
18602
18820
  console.log(`${symbol} ${stripControl(message)}`);
18603
18821
  }
18822
+ symbol(unicode, ascii) {
18823
+ return this.noUnicode ? ascii : unicode;
18824
+ }
18604
18825
  text(message) {
18826
+ this.stopActiveSpinner();
18605
18827
  console.log(stripControl(message));
18606
18828
  }
18829
+ separator(width = 60) {
18830
+ this.stopActiveSpinner();
18831
+ if (width <= 0) {
18832
+ console.log("");
18833
+ return;
18834
+ }
18835
+ console.log(this.format(this.symbol("\u2500", "-").repeat(width), source_default.gray));
18836
+ }
18837
+ stopActiveSpinner() {
18838
+ if (this.activeSpinner) {
18839
+ const spinner = this.activeSpinner;
18840
+ this.activeSpinner = null;
18841
+ spinner.stop();
18842
+ }
18843
+ }
18607
18844
  truncate(text, maxLength, suffix = "...") {
18608
18845
  if (maxLength <= 0 || text.length <= maxLength) {
18609
18846
  return text;
@@ -18615,13 +18852,11 @@ class OutputService {
18615
18852
  }
18616
18853
  formatDate(date) {
18617
18854
  const d = typeof date === "string" ? new Date(date) : date;
18618
- return d.toLocaleDateString("en-US", {
18619
- year: "numeric",
18620
- month: "short",
18621
- day: "numeric",
18622
- hour: "2-digit",
18623
- minute: "2-digit"
18624
- });
18855
+ try {
18856
+ return d.toLocaleDateString(this.locale, DATE_FORMAT_OPTIONS);
18857
+ } catch {
18858
+ return d.toLocaleDateString(DEFAULT_LOCALE, DATE_FORMAT_OPTIONS);
18859
+ }
18625
18860
  }
18626
18861
  format(text, formatter) {
18627
18862
  if (this.noColor) {
@@ -22650,7 +22885,7 @@ function redactRequestUrl(requestUrl, baseUrl) {
22650
22885
  return queryIdx === -1 ? raw : `${raw.slice(0, queryIdx)}?[redacted]`;
22651
22886
  }
22652
22887
  }
22653
- function createApiClient(credentialStore, oauthService) {
22888
+ function createApiClient(credentialStore, output, oauthService) {
22654
22889
  const instance = axios_default.create({
22655
22890
  baseURL: BASE_URL,
22656
22891
  headers: {
@@ -22714,7 +22949,9 @@ function createApiClient(credentialStore, oauthService) {
22714
22949
  const delay = getRetryDelay(error, config.__retryCount);
22715
22950
  const status = error.response.status;
22716
22951
  const label = status === 429 ? "Rate limited" : `Server error (${status})`;
22717
- console.error(`${label}, retrying in ${(delay / 1000).toFixed(1)}s (attempt ${config.__retryCount}/${MAX_RETRIES})...`);
22952
+ if (!output.isJsonMode()) {
22953
+ output.warning(`${label}, retrying in ${(delay / 1000).toFixed(1)}s (attempt ${config.__retryCount}/${MAX_RETRIES})...`);
22954
+ }
22718
22955
  await sleep(delay);
22719
22956
  return instance(config);
22720
22957
  }
@@ -27245,6 +27482,7 @@ class BaseCommand {
27245
27482
  }
27246
27483
  async run(options, context) {
27247
27484
  this.output.setJsonFormatOptions({
27485
+ json: !!context.globalOptions.json,
27248
27486
  fields: context.globalOptions.jsonFields,
27249
27487
  jq: context.globalOptions.jq
27250
27488
  });
@@ -27340,6 +27578,21 @@ class BaseCommand {
27340
27578
  }
27341
27579
  return parsed;
27342
27580
  }
27581
+ truncateText(text, maxLength, opts = {}) {
27582
+ if (opts.noTruncate) {
27583
+ return text;
27584
+ }
27585
+ return this.output.truncate(text, maxLength);
27586
+ }
27587
+ requireConfirmation(confirmed, warning) {
27588
+ if (confirmed)
27589
+ return;
27590
+ throw new BBError({
27591
+ code: 5001 /* VALIDATION_REQUIRED */,
27592
+ message: `${warning}
27593
+ Use --yes to confirm.`
27594
+ });
27595
+ }
27343
27596
  parseEnumOption(value, name, allowed) {
27344
27597
  if (!allowed.includes(value)) {
27345
27598
  throw new BBError({
@@ -27628,7 +27881,12 @@ class CloneCommand extends BaseCommand {
27628
27881
  async execute(options, context) {
27629
27882
  const { repository, directory } = options;
27630
27883
  const repoUrl = await this.resolveRepositoryUrl(repository);
27631
- await this.gitService.clone(repoUrl, directory);
27884
+ const spinner = this.output.spinner(`Cloning ${repository}...`).start();
27885
+ try {
27886
+ await this.gitService.clone(repoUrl, directory);
27887
+ } finally {
27888
+ spinner.stop();
27889
+ }
27632
27890
  const targetDir = directory || this.extractRepoName(repository);
27633
27891
  if (context.globalOptions.json) {
27634
27892
  await this.output.json({
@@ -28041,13 +28299,13 @@ class ListReposCommand extends BaseCommand {
28041
28299
  return;
28042
28300
  }
28043
28301
  if (repos.length === 0) {
28044
- this.output.text("No repositories found");
28302
+ this.output.info("No repositories found");
28045
28303
  return;
28046
28304
  }
28047
28305
  const rows = repos.map((repo) => [
28048
28306
  repo.full_name ?? "",
28049
28307
  repo.is_private ? "private" : "public",
28050
- (repo.description || "").substring(0, 50)
28308
+ this.truncateText(repo.description ?? "", 50, context.globalOptions)
28051
28309
  ]);
28052
28310
  this.output.table(["REPOSITORY", "VISIBILITY", "DESCRIPTION"], rows);
28053
28311
  }
@@ -28132,13 +28390,7 @@ class DeleteRepoCommand extends BaseCommand {
28132
28390
  contextOptions.repo = repository;
28133
28391
  }
28134
28392
  const repoContext = await this.contextService.requireRepoContext(contextOptions);
28135
- if (!yes) {
28136
- throw new BBError({
28137
- code: 5001 /* VALIDATION_REQUIRED */,
28138
- message: `This will permanently delete ${repoContext.workspace}/${repoContext.repoSlug}.
28139
- ` + "Use --yes to confirm deletion."
28140
- });
28141
- }
28393
+ this.requireConfirmation(yes, `This will permanently delete ${repoContext.workspace}/${repoContext.repoSlug}.`);
28142
28394
  await this.repositoriesApi.repositoriesWorkspaceRepoSlugDelete({
28143
28395
  workspace: repoContext.workspace,
28144
28396
  repoSlug: repoContext.repoSlug
@@ -28245,13 +28497,7 @@ class RemoveDefaultReviewerCommand extends BaseCommand {
28245
28497
  }
28246
28498
  async execute(options, context) {
28247
28499
  const repoContext = await this.contextService.requireRepoContextFor(options, context);
28248
- if (!options.yes) {
28249
- throw new BBError({
28250
- code: 5001 /* VALIDATION_REQUIRED */,
28251
- message: `This will remove ${options.username} from the default reviewers of ` + `${repoContext.workspace}/${repoContext.repoSlug}.
28252
- ` + "Use --yes to confirm."
28253
- });
28254
- }
28500
+ this.requireConfirmation(options.yes, `This will remove ${options.username} from the default reviewers of ` + `${repoContext.workspace}/${repoContext.repoSlug}.`);
28255
28501
  const userResponse = await this.usersApi.usersSelectedUserGet({
28256
28502
  selectedUser: options.username
28257
28503
  });
@@ -28327,12 +28573,18 @@ class CreatePRCommand extends BaseCommand {
28327
28573
  if (reviewers.length > 0) {
28328
28574
  request.reviewers = reviewers.map((r) => ({ type: "user", uuid: r.uuid }));
28329
28575
  }
28330
- const response = await this.pullrequestsApi.repositoriesWorkspaceRepoSlugPullrequestsPost({
28331
- workspace: repoContext.workspace,
28332
- repoSlug: repoContext.repoSlug,
28333
- pullrequest: request
28334
- });
28335
- const pr = response.data;
28576
+ const spinner = this.output.spinner("Creating pull request...").start();
28577
+ let pr;
28578
+ try {
28579
+ const response = await this.pullrequestsApi.repositoriesWorkspaceRepoSlugPullrequestsPost({
28580
+ workspace: repoContext.workspace,
28581
+ repoSlug: repoContext.repoSlug,
28582
+ pullrequest: request
28583
+ });
28584
+ pr = response.data;
28585
+ } finally {
28586
+ spinner.stop();
28587
+ }
28336
28588
  const links = pr.links;
28337
28589
  if (context.globalOptions.json) {
28338
28590
  await this.output.json(pr);
@@ -28456,18 +28708,19 @@ class ListPRsCommand extends BaseCommand {
28456
28708
  return;
28457
28709
  }
28458
28710
  if (values.length === 0) {
28459
- this.output.text(`No ${state.toLowerCase()} pull requests found`);
28711
+ this.output.info(`No ${state.toLowerCase()} pull requests found`);
28460
28712
  return;
28461
28713
  }
28714
+ const arrow = this.output.symbol("\u2192", "->");
28462
28715
  const rows = values.map((pr) => {
28463
28716
  const title = pr.draft ? `[DRAFT] ${pr.title}` : pr.title;
28464
28717
  const source = pr.source;
28465
28718
  const destination = pr.destination;
28466
28719
  return [
28467
28720
  `#${pr.id}`,
28468
- this.output.truncate(title ?? "", 50),
28721
+ this.truncateText(title ?? "", 50, context.globalOptions),
28469
28722
  pr.author?.display_name ?? "Unknown",
28470
- `${source?.branch?.name ?? "unknown"} \u2192 ${destination?.branch?.name ?? "unknown"}`
28723
+ `${source?.branch?.name ?? "unknown"} ${arrow} ${destination?.branch?.name ?? "unknown"}`
28471
28724
  ];
28472
28725
  });
28473
28726
  this.output.table(["ID", "TITLE", "AUTHOR", "BRANCHES"], rows);
@@ -28519,7 +28772,7 @@ class ViewPRCommand extends BaseCommand {
28519
28772
  const draftLabel = pr.draft ? this.output.yellow(" [DRAFT]") : "";
28520
28773
  this.output.text("");
28521
28774
  this.output.text(`${this.output.bold(`#${pr.id}`)} ${pr.title}${draftLabel} ${stateColor(`[${pr.state}]`)}`);
28522
- this.output.text(this.output.gray("\u2500".repeat(60)));
28775
+ this.output.separator();
28523
28776
  }
28524
28777
  renderDescription(pr) {
28525
28778
  if (pr.description) {
@@ -28532,7 +28785,7 @@ class ViewPRCommand extends BaseCommand {
28532
28785
  const destination = pr.destination;
28533
28786
  const sourceBranch = this.output.cyan(source?.branch?.name ?? "unknown");
28534
28787
  const destBranch = this.output.cyan(destination?.branch?.name ?? "unknown");
28535
- const arrow = this.output.gray(" \u2192 ");
28788
+ const arrow = this.output.gray(this.output.symbol(" \u2192 ", " -> "));
28536
28789
  this.output.text(`${this.output.dim("Branch:")} ${sourceBranch}${arrow}${destBranch}`);
28537
28790
  if (source?.commit?.hash || destination?.commit?.hash) {
28538
28791
  const sourceHash = source?.commit?.hash ? this.output.gray(source.commit.hash.slice(0, 7)) : this.output.gray("unknown");
@@ -28554,7 +28807,7 @@ class ViewPRCommand extends BaseCommand {
28554
28807
  if (mergeCommit?.hash) {
28555
28808
  this.output.text(`${this.output.dim("Merge:")} ${this.output.magenta(mergeCommit.hash.slice(0, 7))}`);
28556
28809
  }
28557
- const closeBranchIndicator = pr.close_source_branch ? this.output.green("\u2713") : this.output.gray("\u2717");
28810
+ const closeBranchIndicator = pr.close_source_branch ? this.output.green(this.output.symbol("\u2713", "yes")) : this.output.gray(this.output.symbol("\u2717", "no"));
28558
28811
  this.output.text(`${this.output.dim("Close Src:")} ${closeBranchIndicator} ${this.output.gray("(close source branch on merge)")}`);
28559
28812
  this.output.text(`${this.output.dim("Activity:")} ${pr.comment_count ?? 0} comments \xB7 ${pr.task_count ?? 0} tasks`);
28560
28813
  }
@@ -28563,7 +28816,7 @@ class ViewPRCommand extends BaseCommand {
28563
28816
  const reviewers = participants.filter((p) => p.role === "REVIEWER");
28564
28817
  if (reviewers.length === 0) {
28565
28818
  this.output.text("");
28566
- this.output.text(this.output.gray("No reviewers assigned"));
28819
+ this.output.info("No reviewers assigned");
28567
28820
  return;
28568
28821
  }
28569
28822
  this.output.text("");
@@ -28578,17 +28831,26 @@ class ViewPRCommand extends BaseCommand {
28578
28831
  }
28579
28832
  getReviewerStatus(reviewer) {
28580
28833
  if (reviewer.approved) {
28581
- return { icon: this.output.green("\u2713"), label: "approved" };
28834
+ return {
28835
+ icon: this.output.green(this.output.symbol("\u2713", "[OK]")),
28836
+ label: "approved"
28837
+ };
28582
28838
  }
28583
28839
  if (reviewer.state === "changes_requested") {
28584
- return { icon: this.output.red("\u2717"), label: "changes requested" };
28840
+ return {
28841
+ icon: this.output.red(this.output.symbol("\u2717", "[X]")),
28842
+ label: "changes requested"
28843
+ };
28585
28844
  }
28586
- return { icon: this.output.yellow("\u25CB"), label: "pending" };
28845
+ return {
28846
+ icon: this.output.yellow(this.output.symbol("\u25CB", "[ ]")),
28847
+ label: "pending"
28848
+ };
28587
28849
  }
28588
28850
  renderFooter(pr) {
28589
28851
  const links = pr.links;
28590
28852
  this.output.text("");
28591
- this.output.text(this.output.gray("\u2500".repeat(60)));
28853
+ this.output.separator();
28592
28854
  this.output.text(`${this.output.dim("URL:")} ${this.output.underline(this.output.blue(links?.html?.href ?? ""))}`);
28593
28855
  this.output.text("");
28594
28856
  }
@@ -28699,7 +28961,7 @@ class EditPRCommand extends BaseCommand {
28699
28961
  this.output.success(`Updated pull request #${pr.id}`);
28700
28962
  this.output.text(` ${this.output.dim("Title:")} ${pr.title}`);
28701
28963
  if (pr.description) {
28702
- const truncatedDesc = this.output.truncate(pr.description, 100);
28964
+ const truncatedDesc = this.truncateText(pr.description, 100, context.globalOptions);
28703
28965
  this.output.text(` ${this.output.dim("Description:")} ${truncatedDesc}`);
28704
28966
  }
28705
28967
  this.output.text(` ${this.output.dim("URL:")} ${links?.html?.href}`);
@@ -28734,13 +28996,19 @@ class MergePRCommand extends BaseCommand {
28734
28996
  if (options.strategy) {
28735
28997
  request.merge_strategy = this.parseEnumOption(options.strategy, "strategy", VALID_STRATEGIES);
28736
28998
  }
28737
- const response = await this.pullrequestsApi.repositoriesWorkspaceRepoSlugPullrequestsPullRequestIdMergePost({
28738
- workspace: repoContext.workspace,
28739
- repoSlug: repoContext.repoSlug,
28740
- pullRequestId: prId,
28741
- pullrequestMergeParameters: request
28742
- });
28743
- const pr = response.data;
28999
+ const spinner = this.output.spinner(`Merging pull request #${prId}...`).start();
29000
+ let pr;
29001
+ try {
29002
+ const response = await this.pullrequestsApi.repositoriesWorkspaceRepoSlugPullrequestsPullRequestIdMergePost({
29003
+ workspace: repoContext.workspace,
29004
+ repoSlug: repoContext.repoSlug,
29005
+ pullRequestId: prId,
29006
+ pullrequestMergeParameters: request
29007
+ });
29008
+ pr = response.data;
29009
+ } finally {
29010
+ spinner.stop();
29011
+ }
28744
29012
  if (context.globalOptions.json) {
28745
29013
  await this.output.json({
28746
29014
  success: true,
@@ -29200,7 +29468,7 @@ class ActivityPRCommand extends BaseCommand {
29200
29468
  activityType.toUpperCase(),
29201
29469
  this.getActorName(activity),
29202
29470
  this.formatActivityDate(activity),
29203
- this.buildActivityDetails(activity, activityType)
29471
+ this.buildActivityDetails(activity, activityType, context.globalOptions)
29204
29472
  ];
29205
29473
  });
29206
29474
  this.output.table(["TYPE", "ACTOR", "DATE", "DETAILS"], rows);
@@ -29255,19 +29523,19 @@ class ActivityPRCommand extends BaseCommand {
29255
29523
  }
29256
29524
  return this.output.formatDate(date);
29257
29525
  }
29258
- buildActivityDetails(activity, type) {
29526
+ buildActivityDetails(activity, type, globalOptions) {
29259
29527
  switch (type) {
29260
29528
  case "comment": {
29261
29529
  const content = getRawContent(activity.comment?.content) ?? "";
29262
29530
  const id = activity.comment?.id ? `#${activity.comment.id}` : "";
29263
- const snippet = this.output.truncate(content, 80);
29531
+ const snippet = this.truncateText(content, 80, globalOptions);
29264
29532
  return [id, snippet].filter(Boolean).join(" ");
29265
29533
  }
29266
29534
  case "approval":
29267
29535
  return "approved";
29268
29536
  case "changes_requested": {
29269
29537
  const reason = activity.changes_requested?.reason;
29270
- return reason ? this.output.truncate(reason, 80) : "changes requested";
29538
+ return reason ? this.truncateText(reason, 80, globalOptions) : "changes requested";
29271
29539
  }
29272
29540
  case "merge":
29273
29541
  return this.formatCommitDetail(activity.merge?.commit?.hash, "merged");
@@ -29280,7 +29548,7 @@ class ActivityPRCommand extends BaseCommand {
29280
29548
  return `state: ${activity.update.state}`;
29281
29549
  }
29282
29550
  if (activity.update?.title) {
29283
- return `title: ${this.output.truncate(activity.update.title, 60)}`;
29551
+ return `title: ${this.truncateText(activity.update.title, 60, globalOptions)}`;
29284
29552
  }
29285
29553
  if (activity.update?.description) {
29286
29554
  return "description updated";
@@ -29423,7 +29691,7 @@ class ListCommentsPRCommand extends BaseCommand {
29423
29691
  return [
29424
29692
  comment.id?.toString() ?? "",
29425
29693
  getUserDisplayName(comment.user) ?? "Unknown",
29426
- comment.deleted ? "[deleted]" : options.truncate === false ? content : this.output.truncate(content, 60),
29694
+ comment.deleted ? "[deleted]" : this.truncateText(content, 60, context.globalOptions),
29427
29695
  this.output.formatDate(comment.created_on ?? "")
29428
29696
  ];
29429
29697
  });
@@ -29486,13 +29754,7 @@ class DeleteCommentPRCommand extends BaseCommand {
29486
29754
  const repoContext = await this.contextService.requireRepoContextFor(options, context);
29487
29755
  const prId = this.parsePositiveInt(options.prId, "pr-id");
29488
29756
  const commentId = this.parsePositiveInt(options.commentId, "comment-id");
29489
- if (!options.yes) {
29490
- throw new BBError({
29491
- code: 5001 /* VALIDATION_REQUIRED */,
29492
- message: `This will permanently delete comment #${commentId} on PR #${prId}.
29493
- ` + "Use --yes to confirm deletion."
29494
- });
29495
- }
29757
+ this.requireConfirmation(options.yes, `This will permanently delete comment #${commentId} on PR #${prId}.`);
29496
29758
  await this.pullrequestsApi.repositoriesWorkspaceRepoSlugPullrequestsPullRequestIdCommentsCommentIdDelete({
29497
29759
  workspace: repoContext.workspace,
29498
29760
  repoSlug: repoContext.repoSlug,
@@ -29666,7 +29928,7 @@ class ChecksPRCommand extends BaseCommand {
29666
29928
  return;
29667
29929
  }
29668
29930
  this.renderHeader(prId, statuses.length);
29669
- this.renderStatuses(statuses, summary);
29931
+ this.renderStatuses(statuses, summary, context.globalOptions);
29670
29932
  }
29671
29933
  formatStatusForJson(status) {
29672
29934
  return {
@@ -29685,9 +29947,9 @@ class ChecksPRCommand extends BaseCommand {
29685
29947
  this.output.text("");
29686
29948
  const title = this.output.bold("Pull Request #" + prId);
29687
29949
  this.output.text(`${title} - ${count} check${count === 1 ? "" : "s"}`);
29688
- this.output.text(this.output.gray("-".repeat(60)));
29950
+ this.output.separator();
29689
29951
  }
29690
- renderStatuses(statuses, summary) {
29952
+ renderStatuses(statuses, summary, globalOptions) {
29691
29953
  const rows = statuses.map((status) => {
29692
29954
  const stateIcon = this.getStateIcon(status.state);
29693
29955
  const stateLabel = this.getStateLabel(status.state);
@@ -29696,7 +29958,7 @@ class ChecksPRCommand extends BaseCommand {
29696
29958
  return [
29697
29959
  `${stateIcon} ${stateLabel}`,
29698
29960
  this.output.bold(name),
29699
- this.output.truncate(description, 40),
29961
+ this.truncateText(description, 40, globalOptions),
29700
29962
  status.updated_on ? this.output.formatDate(status.updated_on) : "-"
29701
29963
  ];
29702
29964
  });
@@ -29786,7 +30048,7 @@ class ListSnippetsCommand extends BaseCommand {
29786
30048
  return;
29787
30049
  }
29788
30050
  if (snippets.length === 0) {
29789
- this.output.text("No snippets found");
30051
+ this.output.info("No snippets found");
29790
30052
  return;
29791
30053
  }
29792
30054
  const rows = snippets.map((snippet) => [
@@ -29847,9 +30109,10 @@ class ViewSnippetCommand extends BaseCommand {
29847
30109
  return;
29848
30110
  }
29849
30111
  this.renderSnippet(snippet, fileNames);
30112
+ const fileSep = this.output.symbol("\u2500\u2500", "--");
29850
30113
  for (const name of fileNames) {
29851
30114
  this.output.text("");
29852
- this.output.text(this.output.bold(`\u2500\u2500 ${name} \u2500\u2500`));
30115
+ this.output.text(this.output.bold(`${fileSep} ${name} ${fileSep}`));
29853
30116
  this.output.text(contents[name] ?? "");
29854
30117
  }
29855
30118
  return;
@@ -29871,7 +30134,7 @@ class ViewSnippetCommand extends BaseCommand {
29871
30134
  const visibility = snippet.is_private ? "private" : "public";
29872
30135
  this.output.text("");
29873
30136
  this.output.text(`${this.output.bold(String(snippet.id ?? ""))} ${snippet.title ?? "Untitled"} ${this.output.gray(`[${visibility}]`)}`);
29874
- this.output.text(this.output.gray("\u2500".repeat(60)));
30137
+ this.output.separator();
29875
30138
  const creator = getUserDisplayName(snippet.creator);
29876
30139
  if (creator) {
29877
30140
  this.output.text(`Creator: ${creator}`);
@@ -30032,13 +30295,7 @@ class DeleteSnippetCommand extends BaseCommand {
30032
30295
  }
30033
30296
  async execute(options, context) {
30034
30297
  const workspace = await this.contextService.requireWorkspace(options.workspace ?? context.globalOptions.workspace);
30035
- if (!options.yes) {
30036
- throw new BBError({
30037
- code: 5001 /* VALIDATION_REQUIRED */,
30038
- message: `This will permanently delete snippet ${options.id}.
30039
- ` + "Use --yes to confirm deletion."
30040
- });
30041
- }
30298
+ this.requireConfirmation(options.yes, `This will permanently delete snippet ${options.id}.`);
30042
30299
  await this.snippetsApi.snippetsWorkspaceEncodedIdDelete({
30043
30300
  workspace,
30044
30301
  encodedId: options.id
@@ -30158,7 +30415,7 @@ class ListSnippetCommentsCommand extends BaseCommand {
30158
30415
  String(comment.id ?? ""),
30159
30416
  getUserDisplayName(comment.user) ?? "Unknown",
30160
30417
  this.output.formatDate(comment.created_on ?? ""),
30161
- this.output.truncate(content, 60)
30418
+ this.truncateText(content, 60, context.globalOptions)
30162
30419
  ];
30163
30420
  });
30164
30421
  this.output.table(["ID", "AUTHOR", "DATE", "CONTENT"], rows);
@@ -30256,13 +30513,7 @@ class DeleteSnippetCommentCommand extends BaseCommand {
30256
30513
  async execute(options, context) {
30257
30514
  const workspace = await this.contextService.requireWorkspace(options.workspace ?? context.globalOptions.workspace);
30258
30515
  const commentId = this.parsePositiveInt(options.commentId, "comment-id");
30259
- if (!options.yes) {
30260
- throw new BBError({
30261
- code: 5001 /* VALIDATION_REQUIRED */,
30262
- message: `This will permanently delete comment #${commentId} on snippet ${options.snippetId}.
30263
- ` + "Use --yes to confirm deletion."
30264
- });
30265
- }
30516
+ this.requireConfirmation(options.yes, `This will permanently delete comment #${commentId} on snippet ${options.snippetId}.`);
30266
30517
  await this.snippetsApi.snippetsWorkspaceEncodedIdCommentsCommentIdDelete({
30267
30518
  workspace,
30268
30519
  encodedId: options.snippetId,
@@ -30406,7 +30657,7 @@ class ListConfigCommand extends BaseCommand {
30406
30657
  String(value)
30407
30658
  ]);
30408
30659
  if (rows.length === 0) {
30409
- this.output.text("No configuration set");
30660
+ this.output.info("No configuration set");
30410
30661
  } else {
30411
30662
  this.output.table(["KEY", "VALUE"], rows);
30412
30663
  }
@@ -30667,7 +30918,8 @@ function registerApiClient(container, token, ctor) {
30667
30918
  container.register(token, () => {
30668
30919
  const credentialStore = container.resolve(ServiceTokens.CredentialStore);
30669
30920
  const oauthService = container.resolve(ServiceTokens.OAuthService);
30670
- const axiosInstance = createApiClient(credentialStore, oauthService);
30921
+ const outputService = container.resolve(ServiceTokens.OutputService);
30922
+ const axiosInstance = createApiClient(credentialStore, outputService, oauthService);
30671
30923
  return new ctor(undefined, undefined, axiosInstance);
30672
30924
  });
30673
30925
  }
@@ -30682,7 +30934,11 @@ function bootstrap(options = {}) {
30682
30934
  container.register(ServiceTokens.ConfigService, () => new ConfigService);
30683
30935
  container.register(ServiceTokens.CredentialStore, () => container.resolve(ServiceTokens.ConfigService));
30684
30936
  container.register(ServiceTokens.GitService, () => new GitService);
30685
- container.register(ServiceTokens.OutputService, () => new OutputService({ noColor: options.noColor }));
30937
+ container.register(ServiceTokens.OutputService, () => new OutputService({
30938
+ noColor: options.noColor,
30939
+ noUnicode: options.noUnicode,
30940
+ locale: options.locale
30941
+ }));
30686
30942
  registerCommand(container, ServiceTokens.OAuthService, OAuthService, [
30687
30943
  ServiceTokens.ConfigService,
30688
30944
  ServiceTokens.CredentialStore
@@ -30698,7 +30954,8 @@ function bootstrap(options = {}) {
30698
30954
  container.register(ServiceTokens.SnippetsAxios, () => {
30699
30955
  const credentialStore = container.resolve(ServiceTokens.CredentialStore);
30700
30956
  const oauthService = container.resolve(ServiceTokens.OAuthService);
30701
- return createApiClient(credentialStore, oauthService);
30957
+ const outputService = container.resolve(ServiceTokens.OutputService);
30958
+ return createApiClient(credentialStore, outputService, oauthService);
30702
30959
  });
30703
30960
  container.register(ServiceTokens.SnippetsApi, () => {
30704
30961
  const axiosInstance = container.resolve(ServiceTokens.SnippetsAxios);
@@ -31043,8 +31300,11 @@ var ROOT_COMPLETIONS = [
31043
31300
  "--version",
31044
31301
  "--json",
31045
31302
  "--no-color",
31303
+ "--no-unicode",
31304
+ "--no-truncate",
31046
31305
  "--workspace",
31047
- "--repo"
31306
+ "--repo",
31307
+ "--locale"
31048
31308
  ];
31049
31309
  var SUBCOMMAND_COMPLETIONS = new Map([
31050
31310
  ["auth", ["login", "logout", "status", "token"]],
@@ -31111,6 +31371,25 @@ if (process.argv.includes("--get-yargs-completions") || process.env.COMP_LINE) {
31111
31371
  process.exit(0);
31112
31372
  }
31113
31373
  }
31374
+ function extractLocaleArg(argv) {
31375
+ for (let i = 0;i < argv.length; i++) {
31376
+ const arg = argv[i];
31377
+ if (arg === undefined) {
31378
+ continue;
31379
+ }
31380
+ if (arg === "--locale") {
31381
+ const next = argv[i + 1];
31382
+ if (typeof next === "string" && !next.startsWith("-")) {
31383
+ return next;
31384
+ }
31385
+ return;
31386
+ }
31387
+ if (arg.startsWith("--locale=")) {
31388
+ return arg.slice("--locale=".length);
31389
+ }
31390
+ }
31391
+ return;
31392
+ }
31114
31393
  function resolveNoColorSetting(argv, env2) {
31115
31394
  const hasColorArg = argv.includes("--color");
31116
31395
  const hasNoColorArg = argv.includes("--no-color");
@@ -31127,9 +31406,20 @@ function resolveNoColorSetting(argv, env2) {
31127
31406
  }
31128
31407
  return hasNoColorEnv;
31129
31408
  }
31409
+ function resolveNoUnicodeSetting(argv, env2) {
31410
+ if (argv.includes("--no-unicode")) {
31411
+ return true;
31412
+ }
31413
+ return env2.BB_NO_UNICODE !== undefined && env2.BB_NO_UNICODE !== "";
31414
+ }
31130
31415
  var noColor = resolveNoColorSetting(process.argv, process.env);
31416
+ var noUnicode = resolveNoUnicodeSetting(process.argv, process.env);
31131
31417
  var buildHelpText = createHelpTextBuilder(noColor);
31132
- var container = bootstrap({ noColor });
31418
+ var locale = resolveLocale({
31419
+ explicit: extractLocaleArg(process.argv),
31420
+ env: process.env
31421
+ });
31422
+ var container = bootstrap({ noColor, noUnicode, locale });
31133
31423
  function createContext(program2) {
31134
31424
  const opts = program2.opts();
31135
31425
  const jsonOpt = opts.json;
@@ -31160,6 +31450,8 @@ function createContext(program2) {
31160
31450
  jsonFields,
31161
31451
  jq: jqOpt,
31162
31452
  noColor: opts.color === false,
31453
+ noUnicode: opts.unicode === false || noUnicode,
31454
+ noTruncate: opts.truncate === false,
31163
31455
  workspace: opts.workspace,
31164
31456
  repo: opts.repo
31165
31457
  },
@@ -31189,13 +31481,15 @@ function withGlobalOptions(options, context) {
31189
31481
  };
31190
31482
  }
31191
31483
  var cli = new Command;
31192
- cli.name("bb").description("A command-line interface for Bitbucket Cloud").version(pkg2.version).option("--json [fields]", "Output as JSON; optionally project to a comma-separated field list (e.g. number,title,author.display_name)").option("--jq <expression>", `Filter the JSON output through a jq expression \u2014 runs in-process via embedded jq, requires --json (e.g. '.pullRequests[] | select(.state == "OPEN") | .title')`).option("--no-color", "Disable color output").option("-w, --workspace <workspace>", "Specify workspace").option("-r, --repo <repo>", "Specify repository").addHelpText("after", buildHelpText({
31484
+ cli.name("bb").description("A command-line interface for Bitbucket Cloud").version(pkg2.version).option("--json [fields]", "Output as JSON; optionally project to a comma-separated field list (e.g. number,title,author.display_name)").option("--jq <expression>", `Filter the JSON output through a jq expression \u2014 runs in-process via embedded jq, requires --json (e.g. '.pullRequests[] | select(.state == "OPEN") | .title')`).option("--no-color", "Disable color output").option("--no-unicode", "Use ASCII fallbacks for symbols (separators, arrows, status icons) \u2014 also enabled by BB_NO_UNICODE").option("--no-truncate", "Show full values in table output without truncation").option("--locale <locale>", "BCP-47 locale tag for date/time formatting (e.g. de-DE, ja-JP). Falls back to BB_LOCALE, then LC_TIME/LC_ALL/LANG, then en-US.").option("-w, --workspace <workspace>", "Specify workspace").option("-r, --repo <repo>", "Specify repository").addHelpText("after", buildHelpText({
31193
31485
  envVars: {
31194
31486
  BB_USERNAME: "Bitbucket username (fallback for auth login)",
31195
31487
  BB_API_TOKEN: "Bitbucket API token (fallback for auth login)",
31196
31488
  NO_COLOR: "Disable color output when set",
31197
31489
  FORCE_COLOR: "Force color output when set (and not '0')",
31198
- DEBUG: "Enable HTTP debug logging when 'true'"
31490
+ BB_NO_UNICODE: "Use ASCII fallbacks for symbols when set (any non-empty value)",
31491
+ DEBUG: "Enable HTTP debug logging when 'true'",
31492
+ BB_LOCALE: "BCP-47 locale tag for date/time formatting; --locale takes precedence"
31199
31493
  },
31200
31494
  seeAlso: [
31201
31495
  {
@@ -31219,11 +31513,11 @@ cli.name("bb").description("A command-line interface for Bitbucket Cloud").versi
31219
31513
  const result = await versionService.checkForUpdate();
31220
31514
  if (result?.updateAvailable) {
31221
31515
  output.text("");
31222
- output.text("\u2500".repeat(50));
31223
- output.text(`\u26A0 A new version is available: ${result.latestVersion} (you have ${result.currentVersion})`);
31224
- output.text(` Run '${versionService.getInstallCommand()}' to update`);
31225
- output.text(` Or disable with 'bb config set skipVersionCheck true'`);
31226
- output.text("\u2500".repeat(50));
31516
+ output.separator(50);
31517
+ output.warning(`A new version is available: ${result.latestVersion} (you have ${result.currentVersion})
31518
+ ` + ` Run '${versionService.getInstallCommand()}' to update
31519
+ ` + ` Or disable with 'bb config set skipVersionCheck true'`);
31520
+ output.separator(50);
31227
31521
  }
31228
31522
  } catch {}
31229
31523
  try {
@@ -31530,7 +31824,7 @@ prCmd.command("diff [id]").description("View pull request diff").option("--color
31530
31824
  await runCommand(ServiceTokens.DiffPRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
31531
31825
  });
31532
31826
  var prCommentsCmd = new Command("comments").description("Manage pull request comments");
31533
- prCommentsCmd.command("list <id>").description("List comments on a pull request").option("--limit <number>", "Maximum number of comments (default: 25)").option("--no-truncate", "Show full comment content without truncation").addHelpText("after", buildHelpText({
31827
+ prCommentsCmd.command("list <id>").description("List comments on a pull request").option("--limit <number>", "Maximum number of comments (default: 25)").addHelpText("after", buildHelpText({
31534
31828
  examples: [
31535
31829
  "bb pr comments list 42",
31536
31830
  "bb pr comments list 42 --no-truncate",
@@ -31798,5 +32092,5 @@ if (typeof Bun === "undefined") {
31798
32092
  }
31799
32093
  cli.parse(process.argv);
31800
32094
 
31801
- //# debugId=A7729A51C3738B3664756E2164756E21
32095
+ //# debugId=1F677A9695E119AD64756E2164756E21
31802
32096
  //# sourceMappingURL=index.js.map