@pilatos/bitbucket-cli 1.14.0 → 1.16.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/README.md +53 -8
- package/dist/index.js +766 -139
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -17440,6 +17440,8 @@ var ServiceTokens = {
|
|
|
17440
17440
|
AddDefaultReviewerCommand: "AddDefaultReviewerCommand",
|
|
17441
17441
|
RemoveDefaultReviewerCommand: "RemoveDefaultReviewerCommand",
|
|
17442
17442
|
DefaultReviewerService: "DefaultReviewerService",
|
|
17443
|
+
UrlBuilderService: "UrlBuilderService",
|
|
17444
|
+
BrowseCommand: "BrowseCommand",
|
|
17443
17445
|
CreatePRCommand: "CreatePRCommand",
|
|
17444
17446
|
ListPRsCommand: "ListPRsCommand",
|
|
17445
17447
|
ViewPRCommand: "ViewPRCommand",
|
|
@@ -17483,6 +17485,7 @@ var ServiceTokens = {
|
|
|
17483
17485
|
// src/services/config.service.ts
|
|
17484
17486
|
import { posix, win32 } from "path";
|
|
17485
17487
|
import { homedir } from "os";
|
|
17488
|
+
import { randomUUID } from "crypto";
|
|
17486
17489
|
|
|
17487
17490
|
// src/types/errors.ts
|
|
17488
17491
|
class BBError extends Error {
|
|
@@ -17531,6 +17534,12 @@ class APIError extends BBError {
|
|
|
17531
17534
|
}
|
|
17532
17535
|
}
|
|
17533
17536
|
}
|
|
17537
|
+
function rethrowWithNotFoundContext(error, notFoundMessage) {
|
|
17538
|
+
if (error instanceof APIError && error.statusCode === 404) {
|
|
17539
|
+
throw new APIError(notFoundMessage, 404, error.response, error.context);
|
|
17540
|
+
}
|
|
17541
|
+
throw error;
|
|
17542
|
+
}
|
|
17534
17543
|
|
|
17535
17544
|
class GitError extends BBError {
|
|
17536
17545
|
command;
|
|
@@ -17548,13 +17557,19 @@ class GitError extends BBError {
|
|
|
17548
17557
|
}
|
|
17549
17558
|
|
|
17550
17559
|
// src/services/config.service.ts
|
|
17560
|
+
var CONFIG_FILE_MODE = 384;
|
|
17561
|
+
var CONFIG_DIR_MODE = 448;
|
|
17562
|
+
var INSECURE_MODE_MASK = 63;
|
|
17563
|
+
|
|
17551
17564
|
class ConfigService {
|
|
17552
17565
|
configDir;
|
|
17553
17566
|
configFile;
|
|
17567
|
+
platform;
|
|
17554
17568
|
configCache = null;
|
|
17555
17569
|
constructor(configDir, options = {}) {
|
|
17556
17570
|
const platform = options.platform ?? process.platform;
|
|
17557
17571
|
const joinPath = platform === "win32" ? win32.join : posix.join;
|
|
17572
|
+
this.platform = platform;
|
|
17558
17573
|
this.configDir = configDir ?? this.resolveDefaultConfigDir({ ...options, platform });
|
|
17559
17574
|
this.configFile = joinPath(this.configDir, "config.json");
|
|
17560
17575
|
}
|
|
@@ -17575,7 +17590,10 @@ class ConfigService {
|
|
|
17575
17590
|
async ensureConfigDir() {
|
|
17576
17591
|
try {
|
|
17577
17592
|
const fs = await import("fs/promises");
|
|
17578
|
-
await fs.mkdir(this.configDir, {
|
|
17593
|
+
await fs.mkdir(this.configDir, {
|
|
17594
|
+
recursive: true,
|
|
17595
|
+
mode: CONFIG_DIR_MODE
|
|
17596
|
+
});
|
|
17579
17597
|
} catch (error) {
|
|
17580
17598
|
throw new BBError({
|
|
17581
17599
|
code: 4002 /* CONFIG_WRITE_FAILED */,
|
|
@@ -17584,12 +17602,36 @@ class ConfigService {
|
|
|
17584
17602
|
});
|
|
17585
17603
|
}
|
|
17586
17604
|
}
|
|
17605
|
+
async verifyPermissions(path, expectedMode, kind) {
|
|
17606
|
+
if (this.platform === "win32")
|
|
17607
|
+
return;
|
|
17608
|
+
const fs = await import("fs/promises");
|
|
17609
|
+
let stats;
|
|
17610
|
+
try {
|
|
17611
|
+
stats = await fs.stat(path);
|
|
17612
|
+
} catch (error) {
|
|
17613
|
+
if (error.code === "ENOENT")
|
|
17614
|
+
return;
|
|
17615
|
+
throw error;
|
|
17616
|
+
}
|
|
17617
|
+
const mode = stats.mode & 511;
|
|
17618
|
+
if (mode & INSECURE_MODE_MASK) {
|
|
17619
|
+
const actual = mode.toString(8).padStart(3, "0");
|
|
17620
|
+
const expected = expectedMode.toString(8).padStart(3, "0");
|
|
17621
|
+
throw new BBError({
|
|
17622
|
+
code: 4001 /* CONFIG_READ_FAILED */,
|
|
17623
|
+
message: `Config ${kind} has insecure permissions (${actual}); expected ${expected}. Run: chmod ${expected} ${path}`
|
|
17624
|
+
});
|
|
17625
|
+
}
|
|
17626
|
+
}
|
|
17587
17627
|
async getConfig() {
|
|
17588
17628
|
if (this.configCache) {
|
|
17589
17629
|
return this.configCache;
|
|
17590
17630
|
}
|
|
17591
17631
|
try {
|
|
17592
17632
|
const fs = await import("fs/promises");
|
|
17633
|
+
await this.verifyPermissions(this.configDir, CONFIG_DIR_MODE, "directory");
|
|
17634
|
+
await this.verifyPermissions(this.configFile, CONFIG_FILE_MODE, "file");
|
|
17593
17635
|
const data = await fs.readFile(this.configFile, "utf-8");
|
|
17594
17636
|
this.configCache = JSON.parse(data);
|
|
17595
17637
|
return this.configCache;
|
|
@@ -17598,6 +17640,9 @@ class ConfigService {
|
|
|
17598
17640
|
this.configCache = {};
|
|
17599
17641
|
return this.configCache;
|
|
17600
17642
|
}
|
|
17643
|
+
if (error instanceof BBError) {
|
|
17644
|
+
throw error;
|
|
17645
|
+
}
|
|
17601
17646
|
throw new BBError({
|
|
17602
17647
|
code: 4001 /* CONFIG_READ_FAILED */,
|
|
17603
17648
|
message: `Failed to read config file: ${this.configFile}`,
|
|
@@ -17607,13 +17652,20 @@ class ConfigService {
|
|
|
17607
17652
|
}
|
|
17608
17653
|
async setConfig(config) {
|
|
17609
17654
|
await this.ensureConfigDir();
|
|
17655
|
+
const fs = await import("fs/promises");
|
|
17656
|
+
const body = JSON.stringify(config, null, 2);
|
|
17657
|
+
const tmpFile = `${this.configFile}.${randomUUID()}.tmp`;
|
|
17610
17658
|
try {
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17659
|
+
await fs.writeFile(tmpFile, body, {
|
|
17660
|
+
mode: CONFIG_FILE_MODE,
|
|
17661
|
+
flag: "wx"
|
|
17614
17662
|
});
|
|
17663
|
+
await fs.rename(tmpFile, this.configFile);
|
|
17615
17664
|
this.configCache = config;
|
|
17616
17665
|
} catch (error) {
|
|
17666
|
+
try {
|
|
17667
|
+
await fs.unlink(tmpFile);
|
|
17668
|
+
} catch {}
|
|
17617
17669
|
throw new BBError({
|
|
17618
17670
|
code: 4002 /* CONFIG_WRITE_FAILED */,
|
|
17619
17671
|
message: `Failed to write config file: ${this.configFile}`,
|
|
@@ -17627,7 +17679,7 @@ class ConfigService {
|
|
|
17627
17679
|
if (!username || !apiToken) {
|
|
17628
17680
|
throw new BBError({
|
|
17629
17681
|
code: 1001 /* AUTH_REQUIRED */,
|
|
17630
|
-
message: "Authentication required. Run 'bb auth login'
|
|
17682
|
+
message: "Authentication required. Run 'bb auth login' or set BB_USERNAME and BB_API_TOKEN."
|
|
17631
17683
|
});
|
|
17632
17684
|
}
|
|
17633
17685
|
return { username, apiToken };
|
|
@@ -17674,7 +17726,7 @@ class ConfigService {
|
|
|
17674
17726
|
if (!oauthAccessToken || !oauthRefreshToken || !oauthExpiresAt) {
|
|
17675
17727
|
throw new BBError({
|
|
17676
17728
|
code: 1001 /* AUTH_REQUIRED */,
|
|
17677
|
-
message: "OAuth authentication required. Run 'bb auth login'
|
|
17729
|
+
message: "OAuth authentication required. Run 'bb auth login' or set BB_USERNAME and BB_API_TOKEN."
|
|
17678
17730
|
});
|
|
17679
17731
|
}
|
|
17680
17732
|
return {
|
|
@@ -17773,6 +17825,9 @@ class GitService {
|
|
|
17773
17825
|
async getCurrentBranch() {
|
|
17774
17826
|
return this.execOrError(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
17775
17827
|
}
|
|
17828
|
+
async getCurrentCommit() {
|
|
17829
|
+
return this.execOrError(["rev-parse", "HEAD"]);
|
|
17830
|
+
}
|
|
17776
17831
|
async getRemoteUrl(remote = "origin") {
|
|
17777
17832
|
const result = await this.exec(["remote", "get-url", remote]);
|
|
17778
17833
|
if (result.exitCode !== 0) {
|
|
@@ -17797,14 +17852,14 @@ class ContextService {
|
|
|
17797
17852
|
this.configService = configService;
|
|
17798
17853
|
}
|
|
17799
17854
|
parseRemoteUrl(url) {
|
|
17800
|
-
const sshMatch =
|
|
17855
|
+
const sshMatch = /^git@bitbucket\.org:([^/\s]+)\/([^/\s.]+)(?:\.git)?$/.exec(url);
|
|
17801
17856
|
if (sshMatch) {
|
|
17802
17857
|
return {
|
|
17803
17858
|
workspace: sshMatch[1],
|
|
17804
17859
|
repoSlug: sshMatch[2]
|
|
17805
17860
|
};
|
|
17806
17861
|
}
|
|
17807
|
-
const httpsMatch =
|
|
17862
|
+
const httpsMatch = /^https?:\/\/(?:[^@\s]+@)?bitbucket\.org\/([^/\s]+)\/([^/\s.]+)(?:\.git)?$/.exec(url);
|
|
17808
17863
|
if (httpsMatch) {
|
|
17809
17864
|
return {
|
|
17810
17865
|
workspace: httpsMatch[1],
|
|
@@ -17814,29 +17869,48 @@ class ContextService {
|
|
|
17814
17869
|
return null;
|
|
17815
17870
|
}
|
|
17816
17871
|
async getRepoContextFromGit() {
|
|
17872
|
+
const result = await this.inspectGitRepoContext();
|
|
17873
|
+
return result.context;
|
|
17874
|
+
}
|
|
17875
|
+
async inspectGitRepoContext() {
|
|
17817
17876
|
const isRepo = await this.gitService.isRepository();
|
|
17818
17877
|
if (!isRepo) {
|
|
17819
|
-
return null;
|
|
17878
|
+
return { context: null, reason: "not_a_git_repo", remoteUrl: null };
|
|
17820
17879
|
}
|
|
17880
|
+
let remoteUrl;
|
|
17821
17881
|
try {
|
|
17822
|
-
|
|
17823
|
-
return this.parseRemoteUrl(remoteUrl);
|
|
17882
|
+
remoteUrl = await this.gitService.getRemoteUrl();
|
|
17824
17883
|
} catch {
|
|
17825
|
-
return null;
|
|
17884
|
+
return { context: null, reason: "no_remote", remoteUrl: null };
|
|
17885
|
+
}
|
|
17886
|
+
const context = this.parseRemoteUrl(remoteUrl);
|
|
17887
|
+
if (!context) {
|
|
17888
|
+
return { context: null, reason: "remote_not_bitbucket", remoteUrl };
|
|
17826
17889
|
}
|
|
17890
|
+
return { context, reason: null, remoteUrl };
|
|
17827
17891
|
}
|
|
17828
17892
|
async getRepoContext(options) {
|
|
17893
|
+
const result = await this.resolveRepoContext(options);
|
|
17894
|
+
return result.context;
|
|
17895
|
+
}
|
|
17896
|
+
async resolveRepoContext(options) {
|
|
17829
17897
|
if (options.workspace && options.repo) {
|
|
17830
17898
|
return {
|
|
17831
|
-
workspace: options.workspace,
|
|
17832
|
-
|
|
17899
|
+
context: { workspace: options.workspace, repoSlug: options.repo },
|
|
17900
|
+
reason: null,
|
|
17901
|
+
remoteUrl: null
|
|
17833
17902
|
};
|
|
17834
17903
|
}
|
|
17835
|
-
const
|
|
17904
|
+
const gitResult = await this.inspectGitRepoContext();
|
|
17905
|
+
const gitContext = gitResult.context;
|
|
17836
17906
|
if (options.workspace && gitContext) {
|
|
17837
17907
|
return {
|
|
17838
|
-
|
|
17839
|
-
|
|
17908
|
+
context: {
|
|
17909
|
+
workspace: options.workspace,
|
|
17910
|
+
repoSlug: gitContext.repoSlug
|
|
17911
|
+
},
|
|
17912
|
+
reason: null,
|
|
17913
|
+
remoteUrl: gitResult.remoteUrl
|
|
17840
17914
|
};
|
|
17841
17915
|
}
|
|
17842
17916
|
if (options.repo) {
|
|
@@ -17844,22 +17918,40 @@ class ContextService {
|
|
|
17844
17918
|
const workspace = gitContext?.workspace || config.defaultWorkspace;
|
|
17845
17919
|
if (workspace) {
|
|
17846
17920
|
return {
|
|
17847
|
-
workspace,
|
|
17848
|
-
|
|
17921
|
+
context: { workspace, repoSlug: options.repo },
|
|
17922
|
+
reason: null,
|
|
17923
|
+
remoteUrl: gitResult.remoteUrl
|
|
17849
17924
|
};
|
|
17850
17925
|
}
|
|
17851
17926
|
}
|
|
17852
|
-
return
|
|
17927
|
+
return gitResult;
|
|
17853
17928
|
}
|
|
17854
17929
|
async requireRepoContext(options) {
|
|
17855
|
-
const
|
|
17856
|
-
if (!context) {
|
|
17930
|
+
const result = await this.resolveRepoContext(options);
|
|
17931
|
+
if (!result.context) {
|
|
17857
17932
|
throw new BBError({
|
|
17858
17933
|
code: 6001 /* CONTEXT_REPO_NOT_FOUND */,
|
|
17859
|
-
message:
|
|
17934
|
+
message: this.buildRepoNotFoundMessage(result.reason, result.remoteUrl),
|
|
17935
|
+
context: {
|
|
17936
|
+
reason: result.reason ?? "unknown",
|
|
17937
|
+
...result.remoteUrl ? { remoteUrl: result.remoteUrl } : {}
|
|
17938
|
+
}
|
|
17860
17939
|
});
|
|
17861
17940
|
}
|
|
17862
|
-
return context;
|
|
17941
|
+
return result.context;
|
|
17942
|
+
}
|
|
17943
|
+
buildRepoNotFoundMessage(reason, remoteUrl) {
|
|
17944
|
+
const fallback = "Use --workspace and --repo options, or run this command from within a Bitbucket repository.";
|
|
17945
|
+
switch (reason) {
|
|
17946
|
+
case "not_a_git_repo":
|
|
17947
|
+
return `Not in a git repository. ${fallback}`;
|
|
17948
|
+
case "no_remote":
|
|
17949
|
+
return `Git repository has no remote configured. Add a Bitbucket remote with \`git remote add origin <url>\`, or ${fallback.charAt(0).toLowerCase()}${fallback.slice(1)}`;
|
|
17950
|
+
case "remote_not_bitbucket":
|
|
17951
|
+
return `Remote ${remoteUrl ? `'${remoteUrl}' ` : ""}is not a Bitbucket URL. ${fallback}`;
|
|
17952
|
+
default:
|
|
17953
|
+
return `Could not determine repository. ${fallback}`;
|
|
17954
|
+
}
|
|
17863
17955
|
}
|
|
17864
17956
|
async requireWorkspace(explicit) {
|
|
17865
17957
|
if (explicit && explicit.length > 0) {
|
|
@@ -18397,6 +18489,10 @@ function deepGet(source, path) {
|
|
|
18397
18489
|
}
|
|
18398
18490
|
|
|
18399
18491
|
// src/services/output.service.ts
|
|
18492
|
+
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;
|
|
18493
|
+
function stripControl(value) {
|
|
18494
|
+
return value.replace(CONTROL_CHARS, (_match, sgr) => sgr ?? "");
|
|
18495
|
+
}
|
|
18400
18496
|
var WRAPPER_ARRAY_KEYS = [
|
|
18401
18497
|
"pullRequests",
|
|
18402
18498
|
"repositories",
|
|
@@ -18442,36 +18538,38 @@ class OutputService {
|
|
|
18442
18538
|
if (rows.length === 0) {
|
|
18443
18539
|
return;
|
|
18444
18540
|
}
|
|
18445
|
-
const
|
|
18446
|
-
|
|
18541
|
+
const sanitizedHeaders = headers.map(stripControl);
|
|
18542
|
+
const sanitizedRows = rows.map((row) => row.map((cell) => stripControl(cell || "")));
|
|
18543
|
+
const widths = sanitizedHeaders.map((header, index) => {
|
|
18544
|
+
const maxRowWidth = Math.max(...sanitizedRows.map((row) => (row[index] || "").length));
|
|
18447
18545
|
return Math.max(header.length, maxRowWidth);
|
|
18448
18546
|
});
|
|
18449
|
-
const headerRow =
|
|
18547
|
+
const headerRow = sanitizedHeaders.map((header, index) => header.padEnd(widths[index])).join(" ");
|
|
18450
18548
|
console.log(this.format(headerRow, source_default.bold));
|
|
18451
18549
|
console.log(widths.map((width) => "-".repeat(width)).join(" "));
|
|
18452
|
-
for (const row of
|
|
18453
|
-
const formattedRow = row.map((cell, index) =>
|
|
18550
|
+
for (const row of sanitizedRows) {
|
|
18551
|
+
const formattedRow = row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
|
|
18454
18552
|
console.log(formattedRow);
|
|
18455
18553
|
}
|
|
18456
18554
|
}
|
|
18457
18555
|
success(message) {
|
|
18458
18556
|
const symbol = this.format("\u2713", source_default.green);
|
|
18459
|
-
console.log(`${symbol} ${message}`);
|
|
18557
|
+
console.log(`${symbol} ${stripControl(message)}`);
|
|
18460
18558
|
}
|
|
18461
18559
|
error(message) {
|
|
18462
18560
|
const symbol = this.format("\u2717", source_default.red);
|
|
18463
|
-
console.error(`${symbol} ${message}`);
|
|
18561
|
+
console.error(`${symbol} ${stripControl(message)}`);
|
|
18464
18562
|
}
|
|
18465
18563
|
warning(message) {
|
|
18466
18564
|
const symbol = this.format("\u26A0", source_default.yellow);
|
|
18467
|
-
console.warn(`${symbol} ${message}`);
|
|
18565
|
+
console.warn(`${symbol} ${stripControl(message)}`);
|
|
18468
18566
|
}
|
|
18469
18567
|
info(message) {
|
|
18470
18568
|
const symbol = this.format("\u2139", source_default.blue);
|
|
18471
|
-
console.log(`${symbol} ${message}`);
|
|
18569
|
+
console.log(`${symbol} ${stripControl(message)}`);
|
|
18472
18570
|
}
|
|
18473
18571
|
text(message) {
|
|
18474
|
-
console.log(message);
|
|
18572
|
+
console.log(stripControl(message));
|
|
18475
18573
|
}
|
|
18476
18574
|
formatDate(date) {
|
|
18477
18575
|
const d = typeof date === "string" ? new Date(date) : date;
|
|
@@ -22485,6 +22583,17 @@ function getRetryDelay(error, attempt) {
|
|
|
22485
22583
|
function sleep(ms) {
|
|
22486
22584
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22487
22585
|
}
|
|
22586
|
+
function redactRequestUrl(requestUrl, baseUrl) {
|
|
22587
|
+
const raw = requestUrl ?? "";
|
|
22588
|
+
try {
|
|
22589
|
+
const parsed = new URL(raw, baseUrl);
|
|
22590
|
+
const query = parsed.search ? "?[redacted]" : "";
|
|
22591
|
+
return `${parsed.origin}${parsed.pathname}${query}`;
|
|
22592
|
+
} catch {
|
|
22593
|
+
const queryIdx = raw.indexOf("?");
|
|
22594
|
+
return queryIdx === -1 ? raw : `${raw.slice(0, queryIdx)}?[redacted]`;
|
|
22595
|
+
}
|
|
22596
|
+
}
|
|
22488
22597
|
function createApiClient(credentialStore, oauthService) {
|
|
22489
22598
|
const instance = axios_default.create({
|
|
22490
22599
|
baseURL: BASE_URL,
|
|
@@ -22495,7 +22604,7 @@ function createApiClient(credentialStore, oauthService) {
|
|
|
22495
22604
|
});
|
|
22496
22605
|
instance.interceptors.request.use(async (config) => {
|
|
22497
22606
|
if (process.env.DEBUG === "true") {
|
|
22498
|
-
console.debug(`[HTTP] ${config.method?.toUpperCase()} ${config.url}`);
|
|
22607
|
+
console.debug(`[HTTP] ${config.method?.toUpperCase()} ${redactRequestUrl(config.url, config.baseURL)}`);
|
|
22499
22608
|
}
|
|
22500
22609
|
const authMethod = await credentialStore.getAuthMethod();
|
|
22501
22610
|
if (authMethod === "oauth" && oauthService) {
|
|
@@ -22558,11 +22667,17 @@ function createApiClient(credentialStore, oauthService) {
|
|
|
22558
22667
|
if (error.response) {
|
|
22559
22668
|
const { status, data } = error.response;
|
|
22560
22669
|
const message = extractErrorMessage(data) || error.message;
|
|
22561
|
-
|
|
22670
|
+
const method = error.config?.method?.toUpperCase();
|
|
22671
|
+
const url2 = error.config?.url;
|
|
22672
|
+
throw new APIError(message, status, data, {
|
|
22673
|
+
status,
|
|
22674
|
+
...method ? { method } : {},
|
|
22675
|
+
...url2 ? { url: url2 } : {}
|
|
22676
|
+
});
|
|
22562
22677
|
} else if (error.request) {
|
|
22563
22678
|
throw new BBError({
|
|
22564
22679
|
code: 7001 /* NETWORK_ERROR */,
|
|
22565
|
-
message: "Network error: Unable to reach Bitbucket API",
|
|
22680
|
+
message: "Network error: Unable to reach Bitbucket API. Run with DEBUG=true for details. If you're behind a proxy or using a custom CA, check your environment.",
|
|
22566
22681
|
cause: error
|
|
22567
22682
|
});
|
|
22568
22683
|
} else {
|
|
@@ -22592,13 +22707,15 @@ function extractErrorMessage(data) {
|
|
|
22592
22707
|
}
|
|
22593
22708
|
// src/services/oauth.service.ts
|
|
22594
22709
|
import { createServer } from "http";
|
|
22595
|
-
import { randomBytes } from "crypto";
|
|
22710
|
+
import { createHash, randomBytes } from "crypto";
|
|
22596
22711
|
var BITBUCKET_AUTHORIZE_URL = "https://bitbucket.org/site/oauth2/authorize";
|
|
22597
22712
|
var BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token";
|
|
22713
|
+
var CALLBACK_HOST = "127.0.0.1";
|
|
22598
22714
|
var CALLBACK_PORT = 19872;
|
|
22599
22715
|
var CALLBACK_PATH = "/callback";
|
|
22600
22716
|
var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
22601
22717
|
var AUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
22718
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
22602
22719
|
var DEFAULT_CLIENT_ID = "ErUBvNmdYtfVHgW6J4";
|
|
22603
22720
|
var DEFAULT_CLIENT_SECRET = "QnrWypuKXv7YvU7WJwQRza2n2QfHCEw5";
|
|
22604
22721
|
var OAUTH_SCOPES = [
|
|
@@ -22609,7 +22726,36 @@ var OAUTH_SCOPES = [
|
|
|
22609
22726
|
"pullrequest:write"
|
|
22610
22727
|
].join(" ");
|
|
22611
22728
|
function generateState() {
|
|
22612
|
-
return randomBytes(
|
|
22729
|
+
return randomBytes(32).toString("hex");
|
|
22730
|
+
}
|
|
22731
|
+
function base64UrlEncode(buf) {
|
|
22732
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
22733
|
+
}
|
|
22734
|
+
function generatePkcePair() {
|
|
22735
|
+
const verifier = base64UrlEncode(randomBytes(32));
|
|
22736
|
+
const challenge = base64UrlEncode(createHash("sha256").update(verifier).digest());
|
|
22737
|
+
return { verifier, challenge };
|
|
22738
|
+
}
|
|
22739
|
+
var OAUTH_ERROR_DESCRIPTION_MAX_LENGTH = 200;
|
|
22740
|
+
function extractOAuthErrorDescription(body) {
|
|
22741
|
+
let parsed;
|
|
22742
|
+
try {
|
|
22743
|
+
parsed = JSON.parse(body);
|
|
22744
|
+
} catch {
|
|
22745
|
+
return;
|
|
22746
|
+
}
|
|
22747
|
+
if (typeof parsed !== "object" || parsed === null || !("error_description" in parsed)) {
|
|
22748
|
+
return;
|
|
22749
|
+
}
|
|
22750
|
+
const description = parsed.error_description;
|
|
22751
|
+
if (typeof description !== "string") {
|
|
22752
|
+
return;
|
|
22753
|
+
}
|
|
22754
|
+
const sanitized = description.replace(/\s+/g, " ").trim();
|
|
22755
|
+
if (sanitized.length === 0) {
|
|
22756
|
+
return;
|
|
22757
|
+
}
|
|
22758
|
+
return sanitized.length > OAUTH_ERROR_DESCRIPTION_MAX_LENGTH ? `${sanitized.slice(0, OAUTH_ERROR_DESCRIPTION_MAX_LENGTH)}\u2026` : sanitized;
|
|
22613
22759
|
}
|
|
22614
22760
|
|
|
22615
22761
|
class OAuthService {
|
|
@@ -22622,9 +22768,10 @@ class OAuthService {
|
|
|
22622
22768
|
async authorize(clientId, clientSecret) {
|
|
22623
22769
|
const resolvedClientId = clientId ?? await this.getClientId();
|
|
22624
22770
|
const state = generateState();
|
|
22625
|
-
const
|
|
22771
|
+
const { verifier, challenge } = generatePkcePair();
|
|
22772
|
+
const authUrl = this.buildAuthUrl(resolvedClientId, state, challenge);
|
|
22626
22773
|
const { code } = await this.waitForCallback(authUrl, state);
|
|
22627
|
-
const tokenResponse = await this.exchangeCode(code, resolvedClientId, clientSecret);
|
|
22774
|
+
const tokenResponse = await this.exchangeCode(code, resolvedClientId, verifier, clientSecret);
|
|
22628
22775
|
const expiresAt = Math.floor(Date.now() / 1000) + tokenResponse.expires_in;
|
|
22629
22776
|
await this.credentialStore.setOAuthCredentials({
|
|
22630
22777
|
accessToken: tokenResponse.access_token,
|
|
@@ -22654,14 +22801,17 @@ class OAuthService {
|
|
|
22654
22801
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
22655
22802
|
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`
|
|
22656
22803
|
},
|
|
22657
|
-
body: params.toString()
|
|
22804
|
+
body: params.toString(),
|
|
22805
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
22658
22806
|
});
|
|
22659
22807
|
if (!response.ok) {
|
|
22660
22808
|
const errorBody = await response.text();
|
|
22809
|
+
const description = extractOAuthErrorDescription(errorBody);
|
|
22810
|
+
const baseMessage = `Failed to refresh OAuth token. Run 'bb auth login' to re-authenticate.`;
|
|
22661
22811
|
throw new BBError({
|
|
22662
22812
|
code: 1003 /* AUTH_EXPIRED */,
|
|
22663
|
-
message:
|
|
22664
|
-
context: { status: response.status
|
|
22813
|
+
message: description ? `${baseMessage} (${description})` : baseMessage,
|
|
22814
|
+
context: { status: response.status }
|
|
22665
22815
|
});
|
|
22666
22816
|
}
|
|
22667
22817
|
const tokenResponse = await response.json();
|
|
@@ -22674,22 +22824,29 @@ class OAuthService {
|
|
|
22674
22824
|
return tokenResponse.access_token;
|
|
22675
22825
|
}
|
|
22676
22826
|
async revokeToken() {
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22827
|
+
const credentials = await this.credentialStore.getOAuthCredentials();
|
|
22828
|
+
const clientId = await this.getClientId();
|
|
22829
|
+
const clientSecret = await this.getClientSecret();
|
|
22830
|
+
const params = new URLSearchParams({
|
|
22831
|
+
token: credentials.accessToken
|
|
22832
|
+
});
|
|
22833
|
+
const response = await fetch("https://bitbucket.org/site/oauth2/revoke", {
|
|
22834
|
+
method: "POST",
|
|
22835
|
+
headers: {
|
|
22836
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
22837
|
+
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`
|
|
22838
|
+
},
|
|
22839
|
+
body: params.toString(),
|
|
22840
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
22841
|
+
});
|
|
22842
|
+
if (!response.ok) {
|
|
22843
|
+
const errorBody = await response.text().catch(() => "");
|
|
22844
|
+
throw new BBError({
|
|
22845
|
+
code: 7001 /* NETWORK_ERROR */,
|
|
22846
|
+
message: `Failed to revoke OAuth token (HTTP ${response.status}).`,
|
|
22847
|
+
context: { status: response.status, body: errorBody }
|
|
22691
22848
|
});
|
|
22692
|
-
}
|
|
22849
|
+
}
|
|
22693
22850
|
}
|
|
22694
22851
|
async getValidAccessToken() {
|
|
22695
22852
|
const isExpired = await this.credentialStore.isOAuthTokenExpired();
|
|
@@ -22707,13 +22864,15 @@ class OAuthService {
|
|
|
22707
22864
|
const customSecret = await this.configService.getValue("oauthClientSecret");
|
|
22708
22865
|
return customSecret ?? DEFAULT_CLIENT_SECRET;
|
|
22709
22866
|
}
|
|
22710
|
-
buildAuthUrl(clientId, state) {
|
|
22867
|
+
buildAuthUrl(clientId, state, codeChallenge) {
|
|
22711
22868
|
const params = new URLSearchParams({
|
|
22712
22869
|
client_id: clientId,
|
|
22713
22870
|
response_type: "code",
|
|
22714
22871
|
redirect_uri: CALLBACK_URL,
|
|
22715
22872
|
scope: OAUTH_SCOPES,
|
|
22716
|
-
state
|
|
22873
|
+
state,
|
|
22874
|
+
code_challenge: codeChallenge,
|
|
22875
|
+
code_challenge_method: "S256"
|
|
22717
22876
|
});
|
|
22718
22877
|
return `${BITBUCKET_AUTHORIZE_URL}?${params.toString()}`;
|
|
22719
22878
|
}
|
|
@@ -22783,7 +22942,7 @@ class OAuthService {
|
|
|
22783
22942
|
}));
|
|
22784
22943
|
}
|
|
22785
22944
|
});
|
|
22786
|
-
server.listen(CALLBACK_PORT, async () => {
|
|
22945
|
+
server.listen(CALLBACK_PORT, CALLBACK_HOST, async () => {
|
|
22787
22946
|
try {
|
|
22788
22947
|
const open2 = (await Promise.resolve().then(() => (init_open(), exports_open))).default;
|
|
22789
22948
|
await open2(authUrl);
|
|
@@ -22794,12 +22953,13 @@ ${authUrl}
|
|
|
22794
22953
|
});
|
|
22795
22954
|
});
|
|
22796
22955
|
}
|
|
22797
|
-
async exchangeCode(code, clientId, clientSecretOverride) {
|
|
22956
|
+
async exchangeCode(code, clientId, codeVerifier, clientSecretOverride) {
|
|
22798
22957
|
const clientSecret = clientSecretOverride ?? await this.getClientSecret();
|
|
22799
22958
|
const params = new URLSearchParams({
|
|
22800
22959
|
grant_type: "authorization_code",
|
|
22801
22960
|
code,
|
|
22802
|
-
redirect_uri: CALLBACK_URL
|
|
22961
|
+
redirect_uri: CALLBACK_URL,
|
|
22962
|
+
code_verifier: codeVerifier
|
|
22803
22963
|
});
|
|
22804
22964
|
const response = await fetch(BITBUCKET_TOKEN_URL, {
|
|
22805
22965
|
method: "POST",
|
|
@@ -22807,21 +22967,25 @@ ${authUrl}
|
|
|
22807
22967
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
22808
22968
|
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`
|
|
22809
22969
|
},
|
|
22810
|
-
body: params.toString()
|
|
22970
|
+
body: params.toString(),
|
|
22971
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
22811
22972
|
});
|
|
22812
22973
|
if (!response.ok) {
|
|
22813
22974
|
const errorBody = await response.text();
|
|
22975
|
+
const description = extractOAuthErrorDescription(errorBody);
|
|
22976
|
+
const baseMessage = `Failed to exchange authorization code. Please try again.`;
|
|
22814
22977
|
throw new BBError({
|
|
22815
22978
|
code: 1002 /* AUTH_INVALID */,
|
|
22816
|
-
message:
|
|
22817
|
-
context: { status: response.status
|
|
22979
|
+
message: description ? `${baseMessage} (${description})` : baseMessage,
|
|
22980
|
+
context: { status: response.status }
|
|
22818
22981
|
});
|
|
22819
22982
|
}
|
|
22820
22983
|
return await response.json();
|
|
22821
22984
|
}
|
|
22822
22985
|
async fetchUserInfo(accessToken) {
|
|
22823
22986
|
const response = await fetch("https://api.bitbucket.org/2.0/user", {
|
|
22824
|
-
headers: { Authorization: `Bearer ${accessToken}` }
|
|
22987
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
22988
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
22825
22989
|
});
|
|
22826
22990
|
if (!response.ok) {
|
|
22827
22991
|
throw new BBError({
|
|
@@ -23217,6 +23381,67 @@ function accountToEntry(account) {
|
|
|
23217
23381
|
nickname: asUser.nickname
|
|
23218
23382
|
};
|
|
23219
23383
|
}
|
|
23384
|
+
// src/services/url-builder.service.ts
|
|
23385
|
+
var BITBUCKET_WEB_BASE = "https://bitbucket.org";
|
|
23386
|
+
function encodePathSegments(path3) {
|
|
23387
|
+
return path3.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
23388
|
+
}
|
|
23389
|
+
|
|
23390
|
+
class UrlBuilderService {
|
|
23391
|
+
base;
|
|
23392
|
+
constructor(base = BITBUCKET_WEB_BASE) {
|
|
23393
|
+
this.base = base.replace(/\/+$/, "");
|
|
23394
|
+
}
|
|
23395
|
+
repo(ctx) {
|
|
23396
|
+
return this.repoBase(ctx);
|
|
23397
|
+
}
|
|
23398
|
+
src(ctx, branch, path3, line) {
|
|
23399
|
+
const encodedBranch = encodeURIComponent(branch);
|
|
23400
|
+
const trimmedPath = path3?.replace(/^\/+/, "").replace(/\/+$/, "") ?? "";
|
|
23401
|
+
const pathPart = trimmedPath ? `/${encodePathSegments(trimmedPath)}` : "/";
|
|
23402
|
+
const lineFragment = typeof line === "number" && Number.isFinite(line) && line > 0 ? `#lines-${line}` : "";
|
|
23403
|
+
return `${this.repoBase(ctx)}/src/${encodedBranch}${pathPart}${lineFragment}`;
|
|
23404
|
+
}
|
|
23405
|
+
branchList(ctx) {
|
|
23406
|
+
return `${this.repoBase(ctx)}/branches/`;
|
|
23407
|
+
}
|
|
23408
|
+
commit(ctx, sha) {
|
|
23409
|
+
return `${this.repoBase(ctx)}/commits/${encodeURIComponent(sha)}`;
|
|
23410
|
+
}
|
|
23411
|
+
commitList(ctx) {
|
|
23412
|
+
return `${this.repoBase(ctx)}/commits/`;
|
|
23413
|
+
}
|
|
23414
|
+
pullRequest(ctx, id) {
|
|
23415
|
+
return `${this.repoBase(ctx)}/pull-requests/${id}`;
|
|
23416
|
+
}
|
|
23417
|
+
pullRequestList(ctx) {
|
|
23418
|
+
return `${this.repoBase(ctx)}/pull-requests/`;
|
|
23419
|
+
}
|
|
23420
|
+
pipelinesHome(ctx) {
|
|
23421
|
+
return `${this.repoBase(ctx)}/pipelines`;
|
|
23422
|
+
}
|
|
23423
|
+
pipelineRun(ctx, idOrUuid) {
|
|
23424
|
+
return `${this.repoBase(ctx)}/pipelines/results/${encodeURIComponent(idOrUuid)}`;
|
|
23425
|
+
}
|
|
23426
|
+
downloads(ctx) {
|
|
23427
|
+
return `${this.repoBase(ctx)}/downloads/`;
|
|
23428
|
+
}
|
|
23429
|
+
issue(ctx, id) {
|
|
23430
|
+
return `${this.repoBase(ctx)}/issues/${id}`;
|
|
23431
|
+
}
|
|
23432
|
+
issueList(ctx) {
|
|
23433
|
+
return `${this.repoBase(ctx)}/issues`;
|
|
23434
|
+
}
|
|
23435
|
+
wiki(ctx) {
|
|
23436
|
+
return `${this.repoBase(ctx)}/wiki`;
|
|
23437
|
+
}
|
|
23438
|
+
settings(ctx) {
|
|
23439
|
+
return `${this.repoBase(ctx)}/admin`;
|
|
23440
|
+
}
|
|
23441
|
+
repoBase(ctx) {
|
|
23442
|
+
return `${this.base}/${encodeURIComponent(ctx.workspace)}/${encodeURIComponent(ctx.repoSlug)}`;
|
|
23443
|
+
}
|
|
23444
|
+
}
|
|
23220
23445
|
// src/bootstrap.ts
|
|
23221
23446
|
import { createRequire } from "module";
|
|
23222
23447
|
|
|
@@ -27005,13 +27230,32 @@ class BaseCommand {
|
|
|
27005
27230
|
}
|
|
27006
27231
|
requireOption(value, name, message) {
|
|
27007
27232
|
if (value === undefined || value === null || value === "") {
|
|
27233
|
+
const baseMessage = message || `Option --${name} is required`;
|
|
27008
27234
|
throw new BBError({
|
|
27009
27235
|
code: 5001 /* VALIDATION_REQUIRED */,
|
|
27010
|
-
message:
|
|
27236
|
+
message: this.appendHelpHint(baseMessage)
|
|
27011
27237
|
});
|
|
27012
27238
|
}
|
|
27013
27239
|
return value;
|
|
27014
27240
|
}
|
|
27241
|
+
appendHelpHint(message) {
|
|
27242
|
+
const commandPath = this.getCommandPath();
|
|
27243
|
+
const target = commandPath ? `bb ${commandPath} --help` : "bb --help";
|
|
27244
|
+
return `${message} Run \`${target}\` for usage.`;
|
|
27245
|
+
}
|
|
27246
|
+
getCommandPath() {
|
|
27247
|
+
const argv = process.argv.slice(2);
|
|
27248
|
+
const tokens = [];
|
|
27249
|
+
for (const arg of argv) {
|
|
27250
|
+
if (arg.startsWith("-"))
|
|
27251
|
+
break;
|
|
27252
|
+
tokens.push(arg);
|
|
27253
|
+
}
|
|
27254
|
+
if (tokens.length >= 2) {
|
|
27255
|
+
return `${tokens[0]} ${tokens[1]}`;
|
|
27256
|
+
}
|
|
27257
|
+
return tokens.join(" ");
|
|
27258
|
+
}
|
|
27015
27259
|
parseIntOption(value, name) {
|
|
27016
27260
|
const parsed = Number.parseInt(value, 10);
|
|
27017
27261
|
if (Number.isNaN(parsed)) {
|
|
@@ -27112,12 +27356,33 @@ class LoginCommand extends BaseCommand {
|
|
|
27112
27356
|
this.output.success(`Logged in as ${user.display_name} (${user.username})`);
|
|
27113
27357
|
} catch (error) {
|
|
27114
27358
|
await this.credentialStore.clearCredentials();
|
|
27115
|
-
throw
|
|
27116
|
-
code: 1002 /* AUTH_INVALID */,
|
|
27117
|
-
message: `Authentication failed: ${error instanceof Error ? error.message : String(error)}`
|
|
27118
|
-
});
|
|
27359
|
+
throw this.wrapLoginError(error);
|
|
27119
27360
|
}
|
|
27120
27361
|
}
|
|
27362
|
+
wrapLoginError(error) {
|
|
27363
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
27364
|
+
if (error instanceof APIError) {
|
|
27365
|
+
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
27366
|
+
return new BBError({
|
|
27367
|
+
code: 1002 /* AUTH_INVALID */,
|
|
27368
|
+
message: `Invalid username or token: ${detail}. Verify your Bitbucket username and that the API token is current and has the required scopes.`,
|
|
27369
|
+
cause: error
|
|
27370
|
+
});
|
|
27371
|
+
}
|
|
27372
|
+
if (error.statusCode === 429) {
|
|
27373
|
+
return new BBError({
|
|
27374
|
+
code: 2004 /* API_RATE_LIMITED */,
|
|
27375
|
+
message: `Bitbucket API rate-limited: ${detail}. Wait a moment and try again.`,
|
|
27376
|
+
cause: error
|
|
27377
|
+
});
|
|
27378
|
+
}
|
|
27379
|
+
}
|
|
27380
|
+
return new BBError({
|
|
27381
|
+
code: 1002 /* AUTH_INVALID */,
|
|
27382
|
+
message: `Authentication failed: ${detail}`,
|
|
27383
|
+
cause: error instanceof Error ? error : undefined
|
|
27384
|
+
});
|
|
27385
|
+
}
|
|
27121
27386
|
}
|
|
27122
27387
|
|
|
27123
27388
|
// src/commands/auth/logout.command.ts
|
|
@@ -27133,16 +27398,28 @@ class LogoutCommand extends BaseCommand {
|
|
|
27133
27398
|
}
|
|
27134
27399
|
async execute(_options, context) {
|
|
27135
27400
|
const authMethod = await this.credentialStore.getAuthMethod();
|
|
27401
|
+
let revokeFailed = false;
|
|
27136
27402
|
if (authMethod === "oauth") {
|
|
27137
|
-
|
|
27403
|
+
try {
|
|
27404
|
+
await this.oauthService.revokeToken();
|
|
27405
|
+
} catch {
|
|
27406
|
+
revokeFailed = true;
|
|
27407
|
+
}
|
|
27138
27408
|
await this.credentialStore.clearOAuthCredentials();
|
|
27139
27409
|
} else {
|
|
27140
27410
|
await this.credentialStore.clearCredentials();
|
|
27141
27411
|
}
|
|
27142
27412
|
if (context.globalOptions.json) {
|
|
27143
|
-
await this.output.json({
|
|
27413
|
+
await this.output.json({
|
|
27414
|
+
authenticated: false,
|
|
27415
|
+
success: true,
|
|
27416
|
+
revokeFailed: revokeFailed || undefined
|
|
27417
|
+
});
|
|
27144
27418
|
return;
|
|
27145
27419
|
}
|
|
27420
|
+
if (revokeFailed) {
|
|
27421
|
+
this.output.warning("Token revocation failed; the access token may still be valid at Bitbucket. Consider revoking it manually.");
|
|
27422
|
+
}
|
|
27146
27423
|
this.output.success("Logged out of Bitbucket");
|
|
27147
27424
|
}
|
|
27148
27425
|
}
|
|
@@ -27252,7 +27529,7 @@ class TokenCommand extends BaseCommand {
|
|
|
27252
27529
|
if (!credentials.username || !credentials.apiToken) {
|
|
27253
27530
|
throw new BBError({
|
|
27254
27531
|
code: 1001 /* AUTH_REQUIRED */,
|
|
27255
|
-
message: "Not authenticated. Run 'bb auth login'
|
|
27532
|
+
message: "Not authenticated. Run 'bb auth login' or set BB_USERNAME and BB_API_TOKEN."
|
|
27256
27533
|
});
|
|
27257
27534
|
}
|
|
27258
27535
|
const token = Buffer.from(`${credentials.username}:${credentials.apiToken}`).toString("base64");
|
|
@@ -27729,7 +28006,7 @@ class ViewRepoCommand extends BaseCommand {
|
|
|
27729
28006
|
const response = await this.repositoriesApi.repositoriesWorkspaceRepoSlugGet({
|
|
27730
28007
|
workspace: repoContext.workspace,
|
|
27731
28008
|
repoSlug: repoContext.repoSlug
|
|
27732
|
-
});
|
|
28009
|
+
}).catch((error) => rethrowWithNotFoundContext(error, `Repository ${repoContext.workspace}/${repoContext.repoSlug} not found.`));
|
|
27733
28010
|
const repo = response.data;
|
|
27734
28011
|
if (context.globalOptions.json) {
|
|
27735
28012
|
await this.output.json(repo);
|
|
@@ -27952,7 +28229,7 @@ class CreatePRCommand extends BaseCommand {
|
|
|
27952
28229
|
if (!options.title) {
|
|
27953
28230
|
throw new BBError({
|
|
27954
28231
|
code: 5001 /* VALIDATION_REQUIRED */,
|
|
27955
|
-
message: "Pull request title is required. Use --title option."
|
|
28232
|
+
message: this.appendHelpHint("Pull request title is required. Use --title option.")
|
|
27956
28233
|
});
|
|
27957
28234
|
}
|
|
27958
28235
|
const repoContext = await this.contextService.requireRepoContext({
|
|
@@ -28175,7 +28452,7 @@ class ViewPRCommand extends BaseCommand {
|
|
|
28175
28452
|
workspace: repoContext.workspace,
|
|
28176
28453
|
repoSlug: repoContext.repoSlug,
|
|
28177
28454
|
pullRequestId: prId
|
|
28178
|
-
});
|
|
28455
|
+
}).catch((error) => rethrowWithNotFoundContext(error, `Pull request #${prId} not found in ${repoContext.workspace}/${repoContext.repoSlug}.`));
|
|
28179
28456
|
const pr = response.data;
|
|
28180
28457
|
if (context.globalOptions.json) {
|
|
28181
28458
|
await this.output.json(pr);
|
|
@@ -28337,8 +28614,9 @@ class EditPRCommand extends BaseCommand {
|
|
|
28337
28614
|
try {
|
|
28338
28615
|
body = fs7.readFileSync(options.bodyFile, "utf-8");
|
|
28339
28616
|
} catch (err) {
|
|
28617
|
+
const isNotFound = err instanceof Error && err.code === "ENOENT";
|
|
28340
28618
|
throw new BBError({
|
|
28341
|
-
code: 9999 /* UNKNOWN */,
|
|
28619
|
+
code: isNotFound ? 5003 /* FILE_NOT_FOUND */ : 9999 /* UNKNOWN */,
|
|
28342
28620
|
message: `Failed to read file '${options.bodyFile}': ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
28343
28621
|
cause: err instanceof Error ? err : undefined,
|
|
28344
28622
|
context: { bodyFile: options.bodyFile }
|
|
@@ -28608,10 +28886,6 @@ class CheckoutPRCommand extends BaseCommand {
|
|
|
28608
28886
|
}
|
|
28609
28887
|
|
|
28610
28888
|
// src/commands/pr/diff.command.ts
|
|
28611
|
-
import { exec } from "child_process";
|
|
28612
|
-
import { promisify as promisify7 } from "util";
|
|
28613
|
-
var execAsync = promisify7(exec);
|
|
28614
|
-
|
|
28615
28889
|
class DiffPRCommand extends BaseCommand {
|
|
28616
28890
|
pullrequestsApi;
|
|
28617
28891
|
contextService;
|
|
@@ -28722,16 +28996,8 @@ class DiffPRCommand extends BaseCommand {
|
|
|
28722
28996
|
}
|
|
28723
28997
|
async openInBrowser(url2) {
|
|
28724
28998
|
this.output.info(`Opening ${url2} in your browser...`);
|
|
28725
|
-
const
|
|
28726
|
-
|
|
28727
|
-
if (platform2 === "darwin") {
|
|
28728
|
-
command = `open "${url2}"`;
|
|
28729
|
-
} else if (platform2 === "win32") {
|
|
28730
|
-
command = `start "" "${url2}"`;
|
|
28731
|
-
} else {
|
|
28732
|
-
command = `xdg-open "${url2}"`;
|
|
28733
|
-
}
|
|
28734
|
-
await execAsync(command);
|
|
28999
|
+
const open2 = (await Promise.resolve().then(() => (init_open(), exports_open))).default;
|
|
29000
|
+
await open2(url2);
|
|
28735
29001
|
}
|
|
28736
29002
|
async getWebDiffUrl(workspace, repoSlug, prId) {
|
|
28737
29003
|
const prResponse = await this.pullrequestsApi.repositoriesWorkspaceRepoSlugPullrequestsPullRequestIdGet({
|
|
@@ -28961,11 +29227,11 @@ class ActivityPRCommand extends BaseCommand {
|
|
|
28961
29227
|
return activity.type ? activity.type.toLowerCase() : "activity";
|
|
28962
29228
|
}
|
|
28963
29229
|
getActorName(activity) {
|
|
28964
|
-
const user = activity.comment?.user ?? activity.comment?.author ?? activity.approval?.user ?? activity.
|
|
29230
|
+
const user = activity.comment?.user ?? activity.comment?.author ?? activity.approval?.user ?? activity.changes_requested?.user ?? activity.merge?.user ?? activity.decline?.user ?? activity.commit?.author?.user ?? activity.update?.author ?? activity.user;
|
|
28965
29231
|
return getUserDisplayName(user) ?? "Unknown";
|
|
28966
29232
|
}
|
|
28967
29233
|
formatActivityDate(activity) {
|
|
28968
|
-
const date = activity.comment?.created_on ?? activity.approval?.date ?? activity.
|
|
29234
|
+
const date = activity.comment?.created_on ?? activity.approval?.date ?? activity.changes_requested?.date ?? activity.merge?.date ?? activity.decline?.date ?? activity.commit?.date ?? activity.update?.date;
|
|
28969
29235
|
if (!date) {
|
|
28970
29236
|
return "-";
|
|
28971
29237
|
}
|
|
@@ -29034,16 +29300,23 @@ class CommentPRCommand extends BaseCommand {
|
|
|
29034
29300
|
this.contextService = contextService;
|
|
29035
29301
|
}
|
|
29036
29302
|
async execute(options, context) {
|
|
29303
|
+
const validModesNote = "Valid modes: (1) general comment (no flags); (2) file-level comment (--file + --line-to or --line-from); (3) inline comment with line range (--file + --line-from + --line-to).";
|
|
29037
29304
|
if ((options.lineTo || options.lineFrom) && !options.file) {
|
|
29038
29305
|
throw new BBError({
|
|
29039
29306
|
code: 5001 /* VALIDATION_REQUIRED */,
|
|
29040
|
-
message:
|
|
29307
|
+
message: this.appendHelpHint(`--file is required when using --line-to or --line-from. ${validModesNote}`),
|
|
29308
|
+
context: {
|
|
29309
|
+
validModes: ["general", "file", "inline"]
|
|
29310
|
+
}
|
|
29041
29311
|
});
|
|
29042
29312
|
}
|
|
29043
29313
|
if (options.file && !options.lineTo && !options.lineFrom) {
|
|
29044
29314
|
throw new BBError({
|
|
29045
29315
|
code: 5001 /* VALIDATION_REQUIRED */,
|
|
29046
|
-
message:
|
|
29316
|
+
message: this.appendHelpHint(`At least one of --line-to or --line-from is required when using --file. ${validModesNote}`),
|
|
29317
|
+
context: {
|
|
29318
|
+
validModes: ["general", "file", "inline"]
|
|
29319
|
+
}
|
|
29047
29320
|
});
|
|
29048
29321
|
}
|
|
29049
29322
|
if (options.lineTo) {
|
|
@@ -29575,13 +29848,13 @@ class ViewSnippetCommand extends BaseCommand {
|
|
|
29575
29848
|
const response = await this.snippetsApi.snippetsWorkspaceEncodedIdGet({
|
|
29576
29849
|
workspace,
|
|
29577
29850
|
encodedId: options.id
|
|
29578
|
-
});
|
|
29851
|
+
}).catch((error) => rethrowWithNotFoundContext(error, `Snippet ${options.id} not found in workspace ${workspace}.`));
|
|
29579
29852
|
const snippet = response.data;
|
|
29580
29853
|
const fileNames = this.extractFileNames(snippet);
|
|
29581
29854
|
if (options.file !== undefined) {
|
|
29582
29855
|
if (!fileNames.includes(options.file)) {
|
|
29583
29856
|
throw new BBError({
|
|
29584
|
-
code:
|
|
29857
|
+
code: 5003 /* FILE_NOT_FOUND */,
|
|
29585
29858
|
message: `File not found in snippet: ${options.file}`,
|
|
29586
29859
|
context: { file: options.file, available: fileNames }
|
|
29587
29860
|
});
|
|
@@ -29689,7 +29962,7 @@ class CreateSnippetCommand extends BaseCommand {
|
|
|
29689
29962
|
for (const filePath of options.file) {
|
|
29690
29963
|
if (!fs8.existsSync(filePath)) {
|
|
29691
29964
|
throw new BBError({
|
|
29692
|
-
code:
|
|
29965
|
+
code: 5003 /* FILE_NOT_FOUND */,
|
|
29693
29966
|
message: `File not found: ${filePath}`,
|
|
29694
29967
|
context: { file: filePath }
|
|
29695
29968
|
});
|
|
@@ -29748,7 +30021,7 @@ class EditSnippetCommand extends BaseCommand {
|
|
|
29748
30021
|
for (const filePath of options.file) {
|
|
29749
30022
|
if (!fs9.existsSync(filePath)) {
|
|
29750
30023
|
throw new BBError({
|
|
29751
|
-
code:
|
|
30024
|
+
code: 5003 /* FILE_NOT_FOUND */,
|
|
29752
30025
|
message: `File not found: ${filePath}`,
|
|
29753
30026
|
context: { file: filePath }
|
|
29754
30027
|
});
|
|
@@ -30052,7 +30325,7 @@ class GetConfigCommand extends BaseCommand {
|
|
|
30052
30325
|
if (GetConfigCommand.HIDDEN_KEYS.includes(key)) {
|
|
30053
30326
|
throw new BBError({
|
|
30054
30327
|
code: 4003 /* CONFIG_INVALID_KEY */,
|
|
30055
|
-
message: `Cannot display '${key}' -
|
|
30328
|
+
message: `Cannot display '${key}' - it is part of your authentication credentials, not config. ` + `Use 'bb auth token' to retrieve credentials, or run 'bb config list' to see readable keys.`,
|
|
30056
30329
|
context: { key }
|
|
30057
30330
|
});
|
|
30058
30331
|
}
|
|
@@ -30164,9 +30437,11 @@ class ListConfigCommand extends BaseCommand {
|
|
|
30164
30437
|
]);
|
|
30165
30438
|
if (rows.length === 0) {
|
|
30166
30439
|
this.output.text("No configuration set");
|
|
30167
|
-
|
|
30440
|
+
} else {
|
|
30441
|
+
this.output.table(["KEY", "VALUE"], rows);
|
|
30168
30442
|
}
|
|
30169
|
-
this.output.
|
|
30443
|
+
this.output.text("");
|
|
30444
|
+
this.output.text(this.output.dim(`Settable keys: ${SETTABLE_CONFIG_KEYS.join(", ")}. Run 'bb config set --help' for details.`));
|
|
30170
30445
|
}
|
|
30171
30446
|
}
|
|
30172
30447
|
|
|
@@ -30199,7 +30474,7 @@ class InstallCompletionCommand extends BaseCommand {
|
|
|
30199
30474
|
this.output.text("Restart your shell or source your profile to enable completions.");
|
|
30200
30475
|
} catch (error) {
|
|
30201
30476
|
throw new BBError({
|
|
30202
|
-
code:
|
|
30477
|
+
code: 9001 /* COMPLETION_INSTALL_FAILED */,
|
|
30203
30478
|
message: `Failed to install completions: ${error}`,
|
|
30204
30479
|
cause: error instanceof Error ? error : undefined
|
|
30205
30480
|
});
|
|
@@ -30234,7 +30509,7 @@ class UninstallCompletionCommand extends BaseCommand {
|
|
|
30234
30509
|
this.output.success("Shell completions uninstalled successfully!");
|
|
30235
30510
|
} catch (error) {
|
|
30236
30511
|
throw new BBError({
|
|
30237
|
-
code:
|
|
30512
|
+
code: 9002 /* COMPLETION_UNINSTALL_FAILED */,
|
|
30238
30513
|
message: `Failed to uninstall completions: ${error}`,
|
|
30239
30514
|
cause: error instanceof Error ? error : undefined
|
|
30240
30515
|
});
|
|
@@ -30242,6 +30517,193 @@ class UninstallCompletionCommand extends BaseCommand {
|
|
|
30242
30517
|
}
|
|
30243
30518
|
}
|
|
30244
30519
|
|
|
30520
|
+
// src/commands/browse.command.ts
|
|
30521
|
+
var SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
30522
|
+
var PR_NUMBER_PATTERN = /^\d+$/;
|
|
30523
|
+
var PATH_LINE_PATTERN = /^(.+):(\d+)$/;
|
|
30524
|
+
|
|
30525
|
+
class BrowseCommand extends BaseCommand {
|
|
30526
|
+
contextService;
|
|
30527
|
+
gitService;
|
|
30528
|
+
urlBuilder;
|
|
30529
|
+
name = "browse";
|
|
30530
|
+
description = "Open a Bitbucket page (repo, file, PR, commit, etc.) in your browser";
|
|
30531
|
+
constructor(contextService, gitService, urlBuilder, output) {
|
|
30532
|
+
super(output);
|
|
30533
|
+
this.contextService = contextService;
|
|
30534
|
+
this.gitService = gitService;
|
|
30535
|
+
this.urlBuilder = urlBuilder;
|
|
30536
|
+
}
|
|
30537
|
+
async execute(options, context) {
|
|
30538
|
+
const repoContext = await this.contextService.requireRepoContext({
|
|
30539
|
+
...context.globalOptions,
|
|
30540
|
+
...options
|
|
30541
|
+
});
|
|
30542
|
+
this.validateFlagCombination(options);
|
|
30543
|
+
const url2 = await this.resolveUrl(options, repoContext);
|
|
30544
|
+
const useJson = Boolean(context.globalOptions.json);
|
|
30545
|
+
const printOnly = options.browser === false;
|
|
30546
|
+
if (useJson) {
|
|
30547
|
+
await this.output.json({ url: url2 });
|
|
30548
|
+
return { url: url2, opened: false };
|
|
30549
|
+
}
|
|
30550
|
+
if (printOnly) {
|
|
30551
|
+
this.output.text(url2);
|
|
30552
|
+
return { url: url2, opened: false };
|
|
30553
|
+
}
|
|
30554
|
+
await this.openInBrowser(url2);
|
|
30555
|
+
return { url: url2, opened: true };
|
|
30556
|
+
}
|
|
30557
|
+
async resolveUrl(options, ctx) {
|
|
30558
|
+
if (options.pr !== undefined) {
|
|
30559
|
+
return this.urlBuilder.pullRequest(ctx, this.parsePositiveInt(options.pr, "pr"));
|
|
30560
|
+
}
|
|
30561
|
+
if (options.prs || options.pullRequests) {
|
|
30562
|
+
return this.urlBuilder.pullRequestList(ctx);
|
|
30563
|
+
}
|
|
30564
|
+
if (options.branches) {
|
|
30565
|
+
return this.urlBuilder.branchList(ctx);
|
|
30566
|
+
}
|
|
30567
|
+
if (options.commits) {
|
|
30568
|
+
return this.urlBuilder.commitList(ctx);
|
|
30569
|
+
}
|
|
30570
|
+
if (options.commit !== undefined) {
|
|
30571
|
+
const sha = typeof options.commit === "string" && options.commit.length > 0 ? options.commit : await this.gitService.getCurrentCommit();
|
|
30572
|
+
return this.urlBuilder.commit(ctx, sha);
|
|
30573
|
+
}
|
|
30574
|
+
if (options.pipelines) {
|
|
30575
|
+
return this.urlBuilder.pipelinesHome(ctx);
|
|
30576
|
+
}
|
|
30577
|
+
if (options.pipeline !== undefined) {
|
|
30578
|
+
const value = options.pipeline.trim();
|
|
30579
|
+
if (value.length === 0) {
|
|
30580
|
+
throw new BBError({
|
|
30581
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
30582
|
+
message: this.appendHelpHint("--pipeline requires a run id or uuid.")
|
|
30583
|
+
});
|
|
30584
|
+
}
|
|
30585
|
+
return this.urlBuilder.pipelineRun(ctx, value);
|
|
30586
|
+
}
|
|
30587
|
+
if (options.downloads) {
|
|
30588
|
+
return this.urlBuilder.downloads(ctx);
|
|
30589
|
+
}
|
|
30590
|
+
if (options.issue !== undefined) {
|
|
30591
|
+
return this.urlBuilder.issue(ctx, this.parsePositiveInt(options.issue, "issue"));
|
|
30592
|
+
}
|
|
30593
|
+
if (options.issues) {
|
|
30594
|
+
return this.urlBuilder.issueList(ctx);
|
|
30595
|
+
}
|
|
30596
|
+
if (options.wiki) {
|
|
30597
|
+
return this.urlBuilder.wiki(ctx);
|
|
30598
|
+
}
|
|
30599
|
+
if (options.settings) {
|
|
30600
|
+
return this.urlBuilder.settings(ctx);
|
|
30601
|
+
}
|
|
30602
|
+
const target = options.target?.trim();
|
|
30603
|
+
if (target && target.length > 0) {
|
|
30604
|
+
if (PR_NUMBER_PATTERN.test(target)) {
|
|
30605
|
+
return this.urlBuilder.pullRequest(ctx, Number.parseInt(target, 10));
|
|
30606
|
+
}
|
|
30607
|
+
if (SHA_PATTERN.test(target)) {
|
|
30608
|
+
return this.urlBuilder.commit(ctx, target);
|
|
30609
|
+
}
|
|
30610
|
+
const { path: path3, line } = this.parsePathWithLine(target);
|
|
30611
|
+
const branch = await this.resolveBranch(options.branch);
|
|
30612
|
+
return this.urlBuilder.src(ctx, branch, path3, line);
|
|
30613
|
+
}
|
|
30614
|
+
if (options.branch) {
|
|
30615
|
+
return this.urlBuilder.src(ctx, options.branch);
|
|
30616
|
+
}
|
|
30617
|
+
return this.urlBuilder.repo(ctx);
|
|
30618
|
+
}
|
|
30619
|
+
validateFlagCombination(options) {
|
|
30620
|
+
const setFlags = [];
|
|
30621
|
+
if (options.pr !== undefined)
|
|
30622
|
+
setFlags.push("--pr");
|
|
30623
|
+
if (options.prs) {
|
|
30624
|
+
setFlags.push("--prs");
|
|
30625
|
+
} else if (options.pullRequests) {
|
|
30626
|
+
setFlags.push("--pull-requests");
|
|
30627
|
+
}
|
|
30628
|
+
if (options.branches)
|
|
30629
|
+
setFlags.push("--branches");
|
|
30630
|
+
if (options.commit !== undefined)
|
|
30631
|
+
setFlags.push("--commit");
|
|
30632
|
+
if (options.commits)
|
|
30633
|
+
setFlags.push("--commits");
|
|
30634
|
+
if (options.pipelines)
|
|
30635
|
+
setFlags.push("--pipelines");
|
|
30636
|
+
if (options.pipeline !== undefined)
|
|
30637
|
+
setFlags.push("--pipeline");
|
|
30638
|
+
if (options.downloads)
|
|
30639
|
+
setFlags.push("--downloads");
|
|
30640
|
+
if (options.issue !== undefined)
|
|
30641
|
+
setFlags.push("--issue");
|
|
30642
|
+
if (options.issues)
|
|
30643
|
+
setFlags.push("--issues");
|
|
30644
|
+
if (options.wiki)
|
|
30645
|
+
setFlags.push("--wiki");
|
|
30646
|
+
if (options.settings)
|
|
30647
|
+
setFlags.push("--settings");
|
|
30648
|
+
if (setFlags.length > 1) {
|
|
30649
|
+
throw new BBError({
|
|
30650
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
30651
|
+
message: this.appendHelpHint(`Cannot combine ${setFlags.join(" and ")}; pick one resource.`)
|
|
30652
|
+
});
|
|
30653
|
+
}
|
|
30654
|
+
const hasResourceFlag = setFlags.length === 1;
|
|
30655
|
+
const hasTarget = !!options.target?.trim();
|
|
30656
|
+
if (hasResourceFlag && hasTarget) {
|
|
30657
|
+
throw new BBError({
|
|
30658
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
30659
|
+
message: this.appendHelpHint(`Cannot use a positional target with ${setFlags[0]}.`)
|
|
30660
|
+
});
|
|
30661
|
+
}
|
|
30662
|
+
if (hasResourceFlag && options.branch) {
|
|
30663
|
+
throw new BBError({
|
|
30664
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
30665
|
+
message: this.appendHelpHint(`Cannot combine --branch with ${setFlags[0]}.`)
|
|
30666
|
+
});
|
|
30667
|
+
}
|
|
30668
|
+
}
|
|
30669
|
+
parsePathWithLine(target) {
|
|
30670
|
+
const match = PATH_LINE_PATTERN.exec(target);
|
|
30671
|
+
if (match) {
|
|
30672
|
+
const line = Number.parseInt(match[2], 10);
|
|
30673
|
+
if (Number.isFinite(line) && line > 0) {
|
|
30674
|
+
return { path: match[1], line };
|
|
30675
|
+
}
|
|
30676
|
+
}
|
|
30677
|
+
return { path: target };
|
|
30678
|
+
}
|
|
30679
|
+
async resolveBranch(explicit) {
|
|
30680
|
+
if (explicit && explicit.length > 0) {
|
|
30681
|
+
return explicit;
|
|
30682
|
+
}
|
|
30683
|
+
try {
|
|
30684
|
+
return await this.gitService.getCurrentBranch();
|
|
30685
|
+
} catch {
|
|
30686
|
+
return "HEAD";
|
|
30687
|
+
}
|
|
30688
|
+
}
|
|
30689
|
+
parsePositiveInt(value, name) {
|
|
30690
|
+
const parsed = Number.parseInt(value, 10);
|
|
30691
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || String(parsed) !== value.trim()) {
|
|
30692
|
+
throw new BBError({
|
|
30693
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
30694
|
+
message: this.appendHelpHint(`--${name} must be a positive integer.`),
|
|
30695
|
+
context: { [name]: value }
|
|
30696
|
+
});
|
|
30697
|
+
}
|
|
30698
|
+
return parsed;
|
|
30699
|
+
}
|
|
30700
|
+
async openInBrowser(url2) {
|
|
30701
|
+
this.output.info(`Opening ${url2} in your browser...`);
|
|
30702
|
+
const open2 = (await Promise.resolve().then(() => (init_open(), exports_open))).default;
|
|
30703
|
+
await open2(url2);
|
|
30704
|
+
}
|
|
30705
|
+
}
|
|
30706
|
+
|
|
30245
30707
|
// src/bootstrap.ts
|
|
30246
30708
|
var require2 = createRequire(import.meta.url);
|
|
30247
30709
|
var pkg = require2("../package.json");
|
|
@@ -30288,6 +30750,7 @@ function bootstrap(options = {}) {
|
|
|
30288
30750
|
});
|
|
30289
30751
|
registerCommand(container, ServiceTokens.SnippetFilesService, SnippetFilesService, [ServiceTokens.SnippetsAxios]);
|
|
30290
30752
|
registerCommand(container, ServiceTokens.DefaultReviewerService, DefaultReviewerService, [ServiceTokens.PullrequestsApi]);
|
|
30753
|
+
container.register(ServiceTokens.UrlBuilderService, () => new UrlBuilderService);
|
|
30291
30754
|
registerCommand(container, ServiceTokens.LoginCommand, LoginCommand, [
|
|
30292
30755
|
ServiceTokens.CredentialStore,
|
|
30293
30756
|
ServiceTokens.UsersApi,
|
|
@@ -30522,6 +30985,12 @@ function bootstrap(options = {}) {
|
|
|
30522
30985
|
ServiceTokens.OutputService
|
|
30523
30986
|
]);
|
|
30524
30987
|
registerCommand(container, ServiceTokens.ListConfigCommand, ListConfigCommand, [ServiceTokens.ConfigService, ServiceTokens.OutputService]);
|
|
30988
|
+
registerCommand(container, ServiceTokens.BrowseCommand, BrowseCommand, [
|
|
30989
|
+
ServiceTokens.ContextService,
|
|
30990
|
+
ServiceTokens.GitService,
|
|
30991
|
+
ServiceTokens.UrlBuilderService,
|
|
30992
|
+
ServiceTokens.OutputService
|
|
30993
|
+
]);
|
|
30525
30994
|
registerCommand(container, ServiceTokens.InstallCompletionCommand, InstallCompletionCommand, [ServiceTokens.OutputService]);
|
|
30526
30995
|
registerCommand(container, ServiceTokens.UninstallCompletionCommand, UninstallCompletionCommand, [ServiceTokens.OutputService]);
|
|
30527
30996
|
container.register(ServiceTokens.VersionService, () => {
|
|
@@ -30569,6 +31038,15 @@ function createHelpTextBuilder(noColor) {
|
|
|
30569
31038
|
sections.push(` ${c.bold(name.padEnd(maxLen + 2))}${c.dim(desc)}`);
|
|
30570
31039
|
}
|
|
30571
31040
|
}
|
|
31041
|
+
if (config.seeAlso?.length) {
|
|
31042
|
+
if (sections.length)
|
|
31043
|
+
sections.push("");
|
|
31044
|
+
sections.push(c.bold("See also:"));
|
|
31045
|
+
const maxLen = Math.max(...config.seeAlso.map((e) => e.label.length));
|
|
31046
|
+
for (const { label, url: url2 } of config.seeAlso) {
|
|
31047
|
+
sections.push(` ${c.bold(label.padEnd(maxLen + 2))}${c.cyan(url2)}`);
|
|
31048
|
+
}
|
|
31049
|
+
}
|
|
30572
31050
|
return `
|
|
30573
31051
|
` + sections.join(`
|
|
30574
31052
|
`) + `
|
|
@@ -30602,6 +31080,7 @@ var ROOT_COMPLETIONS = [
|
|
|
30602
31080
|
"repo",
|
|
30603
31081
|
"pr",
|
|
30604
31082
|
"snippet",
|
|
31083
|
+
"browse",
|
|
30605
31084
|
"config",
|
|
30606
31085
|
"completion",
|
|
30607
31086
|
"--help",
|
|
@@ -30754,14 +31233,28 @@ function withGlobalOptions(options, context) {
|
|
|
30754
31233
|
};
|
|
30755
31234
|
}
|
|
30756
31235
|
var cli = new Command;
|
|
30757
|
-
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>",
|
|
31236
|
+
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({
|
|
30758
31237
|
envVars: {
|
|
30759
31238
|
BB_USERNAME: "Bitbucket username (fallback for auth login)",
|
|
30760
31239
|
BB_API_TOKEN: "Bitbucket API token (fallback for auth login)",
|
|
30761
31240
|
NO_COLOR: "Disable color output when set",
|
|
30762
31241
|
FORCE_COLOR: "Force color output when set (and not '0')",
|
|
30763
31242
|
DEBUG: "Enable HTTP debug logging when 'true'"
|
|
30764
|
-
}
|
|
31243
|
+
},
|
|
31244
|
+
seeAlso: [
|
|
31245
|
+
{
|
|
31246
|
+
label: "Quick Start",
|
|
31247
|
+
url: "https://bitbucket-cli.paulvanderlei.com/getting-started/quickstart/"
|
|
31248
|
+
},
|
|
31249
|
+
{
|
|
31250
|
+
label: "Scripting",
|
|
31251
|
+
url: "https://bitbucket-cli.paulvanderlei.com/guides/scripting/"
|
|
31252
|
+
},
|
|
31253
|
+
{
|
|
31254
|
+
label: "Changelog",
|
|
31255
|
+
url: "https://bitbucket-cli.paulvanderlei.com/help/changelog/"
|
|
31256
|
+
}
|
|
31257
|
+
]
|
|
30765
31258
|
})).action(async () => {
|
|
30766
31259
|
cli.outputHelp();
|
|
30767
31260
|
const versionService = container.resolve(ServiceTokens.VersionService);
|
|
@@ -30777,9 +31270,23 @@ cli.name("bb").description("A command-line interface for Bitbucket Cloud").versi
|
|
|
30777
31270
|
output.text("\u2500".repeat(50));
|
|
30778
31271
|
}
|
|
30779
31272
|
} catch {}
|
|
31273
|
+
try {
|
|
31274
|
+
const configService = container.resolve(ServiceTokens.ConfigService);
|
|
31275
|
+
const config = await configService.getConfig();
|
|
31276
|
+
const hasBasicAuth = Boolean(config.username && config.apiToken);
|
|
31277
|
+
const hasOAuth = Boolean(config.oauthAccessToken && config.oauthRefreshToken);
|
|
31278
|
+
if (!hasBasicAuth && !hasOAuth) {
|
|
31279
|
+
output.text("");
|
|
31280
|
+
output.text(`Tip: Run '${output.highlight("bb auth login")}' to get started.`);
|
|
31281
|
+
}
|
|
31282
|
+
} catch {}
|
|
30780
31283
|
});
|
|
30781
31284
|
var authCmd = new Command("auth").description("Authenticate with Bitbucket");
|
|
30782
|
-
authCmd.command("login").description("Authenticate with Bitbucket (OAuth or API token)").option("-u, --username <username>", "Bitbucket username (implies API token auth)").option("-p, --password <password>", "Bitbucket API token (implies API token auth)").option("--app-password", "Use API token authentication instead of OAuth").option("--client-id <clientId>", "Custom OAuth consumer client ID").option("--client-secret <clientSecret>", "Custom OAuth consumer client secret").addHelpText("
|
|
31285
|
+
authCmd.command("login").description("Authenticate with Bitbucket (OAuth or API token)").option("-u, --username <username>", "Bitbucket username (implies API token auth)").option("-p, --password <password>", "Bitbucket API token (implies API token auth)").option("--app-password", "Use API token authentication (instead of OAuth). App passwords are deprecated; use API tokens.").option("--client-id <clientId>", "Custom OAuth consumer client ID").option("--client-secret <clientSecret>", "Custom OAuth consumer client secret").addHelpText("before", `
|
|
31286
|
+
Default: OAuth (browser-based, recommended).
|
|
31287
|
+
` + `For CI/CD: API token via --app-password or BB_API_TOKEN env var.
|
|
31288
|
+
` + `Note: Bitbucket app passwords are deprecated; use OAuth or an API token.
|
|
31289
|
+
`).addHelpText("after", buildHelpText({
|
|
30783
31290
|
examples: [
|
|
30784
31291
|
"bb auth login",
|
|
30785
31292
|
"bb auth login --app-password -u myuser -p mytoken",
|
|
@@ -30793,7 +31300,9 @@ authCmd.command("login").description("Authenticate with Bitbucket (OAuth or API
|
|
|
30793
31300
|
})).action(async (options) => {
|
|
30794
31301
|
await runCommand(ServiceTokens.LoginCommand, options, cli);
|
|
30795
31302
|
});
|
|
30796
|
-
authCmd.command("logout").description("Log out of Bitbucket").addHelpText("after", buildHelpText({
|
|
31303
|
+
authCmd.command("logout").description("Log out of Bitbucket").addHelpText("after", buildHelpText({
|
|
31304
|
+
examples: ["bb auth logout", "bb auth logout --json"]
|
|
31305
|
+
})).action(async () => {
|
|
30797
31306
|
await runCommand(ServiceTokens.LogoutCommand, undefined, cli);
|
|
30798
31307
|
});
|
|
30799
31308
|
authCmd.command("status").description("Show authentication status").addHelpText("after", buildHelpText({
|
|
@@ -30901,12 +31410,22 @@ prCmd.command("create").description("Create a pull request").option("-t, --title
|
|
|
30901
31410
|
source: "current git branch",
|
|
30902
31411
|
destination: "main",
|
|
30903
31412
|
"default-reviewers": "false (override with --default-reviewers or config key prCreateIncludeDefaultReviewers)"
|
|
30904
|
-
}
|
|
31413
|
+
},
|
|
31414
|
+
seeAlso: [
|
|
31415
|
+
{
|
|
31416
|
+
label: "Repository Context",
|
|
31417
|
+
url: "https://bitbucket-cli.paulvanderlei.com/guides/repository-context/"
|
|
31418
|
+
},
|
|
31419
|
+
{
|
|
31420
|
+
label: "Default reviewers",
|
|
31421
|
+
url: "https://bitbucket-cli.paulvanderlei.com/commands/repo/#bb-repo-default-reviewers"
|
|
31422
|
+
}
|
|
31423
|
+
]
|
|
30905
31424
|
})).action(async (options) => {
|
|
30906
31425
|
const context = createContext(cli);
|
|
30907
31426
|
await runCommand(ServiceTokens.CreatePRCommand, withGlobalOptions(options, context), cli, context);
|
|
30908
31427
|
});
|
|
30909
|
-
prCmd.command("list").description("List pull requests").option("-s, --state <state>", `Filter by state (${PR_STATES.join(", ")})`, "OPEN").option("--limit <number>", "Maximum number of PRs to list", "25").option("--mine", "Show only PRs where you are a reviewer").addHelpText("after", buildHelpText({
|
|
31428
|
+
prCmd.command("list").description("List pull requests").option("-s, --state <state>", `Filter by state (${PR_STATES.join(", ")})`, "OPEN").option("--limit <number>", "Maximum number of PRs to list", "25").option("--mine", "Show only PRs where you are a reviewer (not authored by you)").addHelpText("after", buildHelpText({
|
|
30910
31429
|
examples: [
|
|
30911
31430
|
"bb pr list",
|
|
30912
31431
|
"bb pr list -s MERGED --limit 10",
|
|
@@ -30916,7 +31435,17 @@ prCmd.command("list").description("List pull requests").option("-s, --state <sta
|
|
|
30916
31435
|
validValues: {
|
|
30917
31436
|
"Valid states": [...PR_STATES]
|
|
30918
31437
|
},
|
|
30919
|
-
defaults: { state: "OPEN", limit: "25" }
|
|
31438
|
+
defaults: { state: "OPEN", limit: "25" },
|
|
31439
|
+
seeAlso: [
|
|
31440
|
+
{
|
|
31441
|
+
label: "Scripting & Automation",
|
|
31442
|
+
url: "https://bitbucket-cli.paulvanderlei.com/guides/scripting/"
|
|
31443
|
+
},
|
|
31444
|
+
{
|
|
31445
|
+
label: "JSON Output",
|
|
31446
|
+
url: "https://bitbucket-cli.paulvanderlei.com/reference/json-output/"
|
|
31447
|
+
}
|
|
31448
|
+
]
|
|
30920
31449
|
})).action(async (options) => {
|
|
30921
31450
|
const context = createContext(cli);
|
|
30922
31451
|
await runCommand(ServiceTokens.ListPRsCommand, withGlobalOptions(options, context), cli, context);
|
|
@@ -30981,24 +31510,50 @@ prCmd.command("merge <id>").description("Merge a pull request").option("-m, --me
|
|
|
30981
31510
|
"rebase_fast_forward",
|
|
30982
31511
|
"rebase_merge"
|
|
30983
31512
|
]
|
|
31513
|
+
},
|
|
31514
|
+
defaults: {
|
|
31515
|
+
strategy: "the repository's configured merge strategy (typically merge_commit)"
|
|
30984
31516
|
}
|
|
30985
31517
|
})).action(async (id, options) => {
|
|
30986
31518
|
const context = createContext(cli);
|
|
30987
31519
|
await runCommand(ServiceTokens.MergePRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
30988
31520
|
});
|
|
30989
|
-
prCmd.command("approve <id>").description("Approve a pull request").addHelpText("after", buildHelpText({
|
|
31521
|
+
prCmd.command("approve <id>").description("Approve a pull request").addHelpText("after", buildHelpText({
|
|
31522
|
+
examples: [
|
|
31523
|
+
"bb pr approve 42",
|
|
31524
|
+
"bb pr approve 42 --json",
|
|
31525
|
+
"bb pr approve 42 -w my-workspace -r my-repo"
|
|
31526
|
+
]
|
|
31527
|
+
})).action(async (id, options) => {
|
|
30990
31528
|
const context = createContext(cli);
|
|
30991
31529
|
await runCommand(ServiceTokens.ApprovePRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
30992
31530
|
});
|
|
30993
|
-
prCmd.command("decline <id>").description("Decline a pull request").addHelpText("after", buildHelpText({
|
|
31531
|
+
prCmd.command("decline <id>").description("Decline a pull request").addHelpText("after", buildHelpText({
|
|
31532
|
+
examples: [
|
|
31533
|
+
"bb pr decline 42",
|
|
31534
|
+
"bb pr decline 42 --json",
|
|
31535
|
+
"bb pr decline 42 -w my-workspace -r my-repo"
|
|
31536
|
+
]
|
|
31537
|
+
})).action(async (id, options) => {
|
|
30994
31538
|
const context = createContext(cli);
|
|
30995
31539
|
await runCommand(ServiceTokens.DeclinePRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
30996
31540
|
});
|
|
30997
|
-
prCmd.command("ready <id>").description("Mark a draft pull request as ready for review").addHelpText("after", buildHelpText({
|
|
31541
|
+
prCmd.command("ready <id>").description("Mark a draft pull request as ready for review").addHelpText("after", buildHelpText({
|
|
31542
|
+
examples: [
|
|
31543
|
+
"bb pr ready 42",
|
|
31544
|
+
"bb pr ready 42 --json",
|
|
31545
|
+
"bb pr ready 42 -w my-workspace -r my-repo"
|
|
31546
|
+
]
|
|
31547
|
+
})).action(async (id, options) => {
|
|
30998
31548
|
const context = createContext(cli);
|
|
30999
31549
|
await runCommand(ServiceTokens.ReadyPRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31000
31550
|
});
|
|
31001
|
-
prCmd.command("checkout <id>").description("Checkout a pull request locally").addHelpText("after", buildHelpText({
|
|
31551
|
+
prCmd.command("checkout <id>").description("Checkout a pull request locally").addHelpText("after", buildHelpText({
|
|
31552
|
+
examples: [
|
|
31553
|
+
"bb pr checkout 42",
|
|
31554
|
+
"bb pr checkout 42 -w my-workspace -r my-repo"
|
|
31555
|
+
]
|
|
31556
|
+
})).action(async (id, options) => {
|
|
31002
31557
|
const context = createContext(cli);
|
|
31003
31558
|
await runCommand(ServiceTokens.CheckoutPRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31004
31559
|
});
|
|
@@ -31040,13 +31595,19 @@ prCommentsCmd.command("add <id> <message>").description("Add a comment to a pull
|
|
|
31040
31595
|
await runCommand(ServiceTokens.CommentPRCommand, withGlobalOptions({ id, message, ...options }, context), cli, context);
|
|
31041
31596
|
});
|
|
31042
31597
|
prCommentsCmd.command("edit <pr-id> <comment-id> <message>").description("Edit a comment on a pull request").addHelpText("after", buildHelpText({
|
|
31043
|
-
examples: [
|
|
31598
|
+
examples: [
|
|
31599
|
+
'bb pr comments edit 42 12345 "Updated comment"',
|
|
31600
|
+
'bb pr comments edit 42 12345 "Updated comment" --json'
|
|
31601
|
+
]
|
|
31044
31602
|
})).action(async (prId, commentId, message, options) => {
|
|
31045
31603
|
const context = createContext(cli);
|
|
31046
31604
|
await runCommand(ServiceTokens.EditCommentPRCommand, withGlobalOptions({ prId, commentId, message }, context), cli, context);
|
|
31047
31605
|
});
|
|
31048
31606
|
prCommentsCmd.command("delete <pr-id> <comment-id>").description("Delete a comment on a pull request").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", buildHelpText({
|
|
31049
|
-
examples: [
|
|
31607
|
+
examples: [
|
|
31608
|
+
"bb pr comments delete 42 12345",
|
|
31609
|
+
"bb pr comments delete 42 12345 --yes"
|
|
31610
|
+
]
|
|
31050
31611
|
})).action(async (prId, commentId, options) => {
|
|
31051
31612
|
const context = createContext(cli);
|
|
31052
31613
|
await runCommand(ServiceTokens.DeleteCommentPRCommand, withGlobalOptions({ prId, commentId, ...options }, context), cli, context);
|
|
@@ -31058,13 +31619,23 @@ prReviewersCmd.command("list <id>").description("List reviewers on a pull reques
|
|
|
31058
31619
|
const context = createContext(cli);
|
|
31059
31620
|
await runCommand(ServiceTokens.ListReviewersPRCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31060
31621
|
});
|
|
31061
|
-
prReviewersCmd.command("add <id> <
|
|
31622
|
+
prReviewersCmd.command("add <id> <user>").description("Add a reviewer to a pull request (user is an account ID or {uuid})").addHelpText("after", buildHelpText({
|
|
31623
|
+
examples: [
|
|
31624
|
+
'bb pr reviewers add 42 "712020:3cfed7e0-0ed6-49fc-bb35-410a00ccee6f"',
|
|
31625
|
+
'bb pr reviewers add 42 "{c1cb1bb5-2e32-456e-a373-43978dc12aa1}"'
|
|
31626
|
+
]
|
|
31627
|
+
})).action(async (id, user, options) => {
|
|
31062
31628
|
const context = createContext(cli);
|
|
31063
|
-
await runCommand(ServiceTokens.AddReviewerPRCommand, withGlobalOptions({ id, username, ...options }, context), cli, context);
|
|
31629
|
+
await runCommand(ServiceTokens.AddReviewerPRCommand, withGlobalOptions({ id, username: user, ...options }, context), cli, context);
|
|
31064
31630
|
});
|
|
31065
|
-
prReviewersCmd.command("remove <id> <
|
|
31631
|
+
prReviewersCmd.command("remove <id> <user>").description("Remove a reviewer from a pull request (user is an account ID or {uuid})").addHelpText("after", buildHelpText({
|
|
31632
|
+
examples: [
|
|
31633
|
+
'bb pr reviewers remove 42 "712020:3cfed7e0-0ed6-49fc-bb35-410a00ccee6f"',
|
|
31634
|
+
'bb pr reviewers remove 42 "{c1cb1bb5-2e32-456e-a373-43978dc12aa1}"'
|
|
31635
|
+
]
|
|
31636
|
+
})).action(async (id, user, options) => {
|
|
31066
31637
|
const context = createContext(cli);
|
|
31067
|
-
await runCommand(ServiceTokens.RemoveReviewerPRCommand, withGlobalOptions({ id, username, ...options }, context), cli, context);
|
|
31638
|
+
await runCommand(ServiceTokens.RemoveReviewerPRCommand, withGlobalOptions({ id, username: user, ...options }, context), cli, context);
|
|
31068
31639
|
});
|
|
31069
31640
|
cli.addCommand(prCmd);
|
|
31070
31641
|
prCmd.addCommand(prCommentsCmd);
|
|
@@ -31095,7 +31666,7 @@ snippetCmd.command("view <id>").description("View snippet details").option("-f,
|
|
|
31095
31666
|
const context = createContext(cli);
|
|
31096
31667
|
await runCommand(ServiceTokens.ViewSnippetCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31097
31668
|
});
|
|
31098
|
-
snippetCmd.command("create").description("Create a new snippet").option("-t, --title <title>", "Snippet title").option("-f, --file <path...>", "File(s) to include").option("--private", "Create a private snippet (default)").option("--public", "Create a public snippet").addHelpText("after", buildHelpText({
|
|
31669
|
+
snippetCmd.command("create").description("Create a new snippet").option("-t, --title <title>", "Snippet title").option("-f, --file <path...>", "File(s) to include (variadic; pass multiple paths or repeat the flag)").option("--private", "Create a private snippet (default)").option("--public", "Create a public snippet").addHelpText("after", buildHelpText({
|
|
31099
31670
|
examples: [
|
|
31100
31671
|
'bb snippet create -t "My snippet" -f file.txt',
|
|
31101
31672
|
'bb snippet create -t "Config files" -f config.yml -f setup.sh --public'
|
|
@@ -31105,7 +31676,7 @@ snippetCmd.command("create").description("Create a new snippet").option("-t, --t
|
|
|
31105
31676
|
const context = createContext(cli);
|
|
31106
31677
|
await runCommand(ServiceTokens.CreateSnippetCommand, withGlobalOptions(options, context), cli, context);
|
|
31107
31678
|
});
|
|
31108
|
-
snippetCmd.command("edit <id>").description("Edit a snippet").option("-t, --title <title>", "New snippet title").option("--private", "Make snippet private").option("--public", "Make snippet public").option("-f, --file <path...>", "Replace/add file(s) (sends multipart update)").addHelpText("after", buildHelpText({
|
|
31679
|
+
snippetCmd.command("edit <id>").description("Edit a snippet").option("-t, --title <title>", "New snippet title").option("--private", "Make snippet private").option("--public", "Make snippet public").option("-f, --file <path...>", "Replace/add file(s) (variadic; pass multiple paths or repeat the flag; sends multipart update)").addHelpText("after", buildHelpText({
|
|
31109
31680
|
examples: [
|
|
31110
31681
|
'bb snippet edit kypj -t "New title"',
|
|
31111
31682
|
"bb snippet edit kypj --public",
|
|
@@ -31121,11 +31692,18 @@ snippetCmd.command("delete <id>").description("Delete a snippet").option("-y, --
|
|
|
31121
31692
|
const context = createContext(cli);
|
|
31122
31693
|
await runCommand(ServiceTokens.DeleteSnippetCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31123
31694
|
});
|
|
31124
|
-
snippetCmd.command("watch <id>").description("Watch a snippet").addHelpText("after", buildHelpText({
|
|
31695
|
+
snippetCmd.command("watch <id>").description("Watch a snippet").addHelpText("after", buildHelpText({
|
|
31696
|
+
examples: ["bb snippet watch kypj", "bb snippet watch kypj -w my-team"]
|
|
31697
|
+
})).action(async (id, options) => {
|
|
31125
31698
|
const context = createContext(cli);
|
|
31126
31699
|
await runCommand(ServiceTokens.WatchSnippetCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31127
31700
|
});
|
|
31128
|
-
snippetCmd.command("unwatch <id>").description("Stop watching a snippet").addHelpText("after", buildHelpText({
|
|
31701
|
+
snippetCmd.command("unwatch <id>").description("Stop watching a snippet").addHelpText("after", buildHelpText({
|
|
31702
|
+
examples: [
|
|
31703
|
+
"bb snippet unwatch kypj",
|
|
31704
|
+
"bb snippet unwatch kypj -w my-team"
|
|
31705
|
+
]
|
|
31706
|
+
})).action(async (id, options) => {
|
|
31129
31707
|
const context = createContext(cli);
|
|
31130
31708
|
await runCommand(ServiceTokens.UnwatchSnippetCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31131
31709
|
});
|
|
@@ -31140,26 +31718,57 @@ snippetCommentsCmd.command("list <id>").description("List comments on a snippet"
|
|
|
31140
31718
|
const context = createContext(cli);
|
|
31141
31719
|
await runCommand(ServiceTokens.ListSnippetCommentsCommand, withGlobalOptions({ id, ...options }, context), cli, context);
|
|
31142
31720
|
});
|
|
31143
|
-
snippetCommentsCmd.command("add <id>").description("Add a comment to a snippet").option("-m, --message <text>", "Comment message").addHelpText("after", buildHelpText({
|
|
31144
|
-
examples: [
|
|
31145
|
-
|
|
31721
|
+
snippetCommentsCmd.command("add <id> [message]").description("Add a comment to a snippet (message is required)").option("-m, --message <text>", "Comment message (alternative to the positional [message] argument; one of the two is required)").addHelpText("after", buildHelpText({
|
|
31722
|
+
examples: [
|
|
31723
|
+
'bb snippet comments add kypj "Great snippet!"',
|
|
31724
|
+
'bb snippet comments add kypj -m "Great snippet!"',
|
|
31725
|
+
'bb snippet comments add kypj "Great snippet!" --json'
|
|
31726
|
+
]
|
|
31727
|
+
})).action(async (id, message, options) => {
|
|
31146
31728
|
const context = createContext(cli);
|
|
31147
|
-
|
|
31729
|
+
const resolvedMessage = message ?? options.message;
|
|
31730
|
+
await runCommand(ServiceTokens.AddSnippetCommentCommand, withGlobalOptions({ id, ...options, message: resolvedMessage }, context), cli, context);
|
|
31148
31731
|
});
|
|
31149
31732
|
snippetCommentsCmd.command("edit <snippet-id> <comment-id> <message>").description("Edit a comment on a snippet").addHelpText("after", buildHelpText({
|
|
31150
|
-
examples: [
|
|
31733
|
+
examples: [
|
|
31734
|
+
'bb snippet comments edit kypj 123 "Updated comment"',
|
|
31735
|
+
'bb snippet comments edit kypj 123 "Updated comment" --json'
|
|
31736
|
+
]
|
|
31151
31737
|
})).action(async (snippetId, commentId, message, options) => {
|
|
31152
31738
|
const context = createContext(cli);
|
|
31153
31739
|
await runCommand(ServiceTokens.EditSnippetCommentCommand, withGlobalOptions({ snippetId, commentId, message }, context), cli, context);
|
|
31154
31740
|
});
|
|
31155
31741
|
snippetCommentsCmd.command("delete <snippet-id> <comment-id>").description("Delete a comment on a snippet").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", buildHelpText({
|
|
31156
|
-
examples: [
|
|
31742
|
+
examples: [
|
|
31743
|
+
"bb snippet comments delete kypj 123",
|
|
31744
|
+
"bb snippet comments delete kypj 123 --yes"
|
|
31745
|
+
]
|
|
31157
31746
|
})).action(async (snippetId, commentId, options) => {
|
|
31158
31747
|
const context = createContext(cli);
|
|
31159
31748
|
await runCommand(ServiceTokens.DeleteSnippetCommentCommand, withGlobalOptions({ snippetId, commentId, ...options }, context), cli, context);
|
|
31160
31749
|
});
|
|
31161
31750
|
snippetCmd.addCommand(snippetCommentsCmd);
|
|
31162
31751
|
cli.addCommand(snippetCmd);
|
|
31752
|
+
cli.command("browse [target]").description("Open a Bitbucket page (repo home, file, PR, commit, etc.) in your browser").option("--pr <id>", "Open a specific pull request").option("--prs", "Open the pull-requests list").option("--pull-requests", "Alias for --prs").option("--branch <name>", "Open the branch source tree (or, with <target>, that path on the branch)").option("--branches", "Open the branches list").option("--commit [sha]", "Open a specific commit (defaults to HEAD when no SHA is given)").option("--commits", "Open the commits list").option("--pipelines", "Open the pipelines page").option("--pipeline <id>", "Open a specific pipeline run").option("--downloads", "Open the downloads page").option("--issue <id>", "Open a specific issue").option("--issues", "Open the issue tracker").option("--wiki", "Open the wiki").option("--settings", "Open repository settings").option("-n, --no-browser", "Print the URL to stdout instead of opening it").addHelpText("after", buildHelpText({
|
|
31753
|
+
examples: [
|
|
31754
|
+
"bb browse",
|
|
31755
|
+
"bb browse src/cli.ts",
|
|
31756
|
+
"bb browse src/cli.ts:42",
|
|
31757
|
+
"bb browse --branch release/2.0 src/cli.ts",
|
|
31758
|
+
"bb browse 217",
|
|
31759
|
+
"bb browse --pr 217",
|
|
31760
|
+
"bb browse --prs",
|
|
31761
|
+
"bb browse abc1234",
|
|
31762
|
+
"bb browse --commit",
|
|
31763
|
+
"bb browse --pipelines",
|
|
31764
|
+
"bb browse --settings",
|
|
31765
|
+
"bb browse --pr 217 --no-browser",
|
|
31766
|
+
"bb browse --pr 217 --json url"
|
|
31767
|
+
]
|
|
31768
|
+
})).action(async (target, options) => {
|
|
31769
|
+
const context = createContext(cli);
|
|
31770
|
+
await runCommand(ServiceTokens.BrowseCommand, withGlobalOptions({ target, ...options }, context), cli, context);
|
|
31771
|
+
});
|
|
31163
31772
|
var configCmd = new Command("config").description("Manage configuration");
|
|
31164
31773
|
configCmd.command("get <key>").description("Get a configuration value").addHelpText("after", buildHelpText({
|
|
31165
31774
|
examples: ["bb config get defaultWorkspace"],
|
|
@@ -31168,7 +31777,8 @@ configCmd.command("get <key>").description("Get a configuration value").addHelpT
|
|
|
31168
31777
|
"username",
|
|
31169
31778
|
"defaultWorkspace",
|
|
31170
31779
|
"skipVersionCheck",
|
|
31171
|
-
"versionCheckInterval"
|
|
31780
|
+
"versionCheckInterval",
|
|
31781
|
+
"prCreateIncludeDefaultReviewers"
|
|
31172
31782
|
]
|
|
31173
31783
|
}
|
|
31174
31784
|
})).action(async (key) => {
|
|
@@ -31184,9 +31794,16 @@ configCmd.command("set <key> <value>").description("Set a configuration value").
|
|
|
31184
31794
|
"Settable config keys": [
|
|
31185
31795
|
"defaultWorkspace (string)",
|
|
31186
31796
|
"skipVersionCheck (true/false)",
|
|
31187
|
-
"versionCheckInterval (positive integer, seconds)"
|
|
31797
|
+
"versionCheckInterval (positive integer, seconds)",
|
|
31798
|
+
"prCreateIncludeDefaultReviewers (true/false)"
|
|
31188
31799
|
]
|
|
31189
|
-
}
|
|
31800
|
+
},
|
|
31801
|
+
seeAlso: [
|
|
31802
|
+
{
|
|
31803
|
+
label: "Configuration File",
|
|
31804
|
+
url: "https://bitbucket-cli.paulvanderlei.com/reference/configuration/"
|
|
31805
|
+
}
|
|
31806
|
+
]
|
|
31190
31807
|
})).action(async (key, value) => {
|
|
31191
31808
|
await runCommand(ServiceTokens.SetConfigCommand, { key, value }, cli);
|
|
31192
31809
|
});
|
|
@@ -31197,10 +31814,20 @@ configCmd.command("list").description("List all configuration values").addHelpTe
|
|
|
31197
31814
|
});
|
|
31198
31815
|
cli.addCommand(configCmd);
|
|
31199
31816
|
var completionCmd = new Command("completion").description("Shell completion utilities");
|
|
31200
|
-
completionCmd.command("install").description("Install shell completions for bash, zsh, or fish").addHelpText("after", buildHelpText({
|
|
31817
|
+
completionCmd.command("install").description("Install shell completions for bash, zsh, or fish").addHelpText("after", buildHelpText({
|
|
31818
|
+
examples: ["bb completion install", "bb completion install --json"],
|
|
31819
|
+
validValues: {
|
|
31820
|
+
"Supported shells": ["bash", "zsh", "fish"]
|
|
31821
|
+
}
|
|
31822
|
+
})).action(async () => {
|
|
31201
31823
|
await runCommand(ServiceTokens.InstallCompletionCommand, undefined, cli);
|
|
31202
31824
|
});
|
|
31203
|
-
completionCmd.command("uninstall").description("Uninstall shell completions").addHelpText("after", buildHelpText({
|
|
31825
|
+
completionCmd.command("uninstall").description("Uninstall shell completions").addHelpText("after", buildHelpText({
|
|
31826
|
+
examples: ["bb completion uninstall", "bb completion uninstall --json"],
|
|
31827
|
+
validValues: {
|
|
31828
|
+
"Supported shells": ["bash", "zsh", "fish"]
|
|
31829
|
+
}
|
|
31830
|
+
})).action(async () => {
|
|
31204
31831
|
await runCommand(ServiceTokens.UninstallCompletionCommand, undefined, cli);
|
|
31205
31832
|
});
|
|
31206
31833
|
cli.addCommand(completionCmd);
|