@pilatos/bitbucket-cli 0.3.2 → 1.0.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 +10 -8
- package/dist/index.js +301 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Manage your Bitbucket authentication securely.
|
|
|
123
123
|
|
|
124
124
|
| Command | Description |
|
|
125
125
|
|---------|-------------|
|
|
126
|
-
| `bb auth login` | Authenticate with Bitbucket using an
|
|
126
|
+
| `bb auth login` | Authenticate with Bitbucket using an API Token |
|
|
127
127
|
| `bb auth logout` | Log out and remove stored credentials |
|
|
128
128
|
| `bb auth status` | Check your current authentication status |
|
|
129
129
|
| `bb auth token` | Print your current access token |
|
|
@@ -211,19 +211,21 @@ These options work with any command:
|
|
|
211
211
|
|
|
212
212
|
## Authentication Setup
|
|
213
213
|
|
|
214
|
-
The CLI uses **Bitbucket
|
|
214
|
+
The CLI uses **Bitbucket API Tokens** for secure authentication. Here's how to set it up:
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
> **Note**: As of September 9, 2025, Bitbucket app passwords are deprecated and can no longer be created. All existing app passwords will be disabled on June 9, 2026. Use API tokens instead.
|
|
217
217
|
|
|
218
|
-
1
|
|
219
|
-
|
|
218
|
+
### Step 1: Create an API Token
|
|
219
|
+
|
|
220
|
+
1. Go to [Bitbucket API Tokens](https://bitbucket.org/account/settings/api-tokens/)
|
|
221
|
+
2. Click **"Create API token"**
|
|
220
222
|
3. Enter a descriptive label (e.g., "bb CLI")
|
|
221
|
-
4. Select the required
|
|
223
|
+
4. Select the required scopes:
|
|
222
224
|
- **Account:** Read
|
|
223
225
|
- **Repositories:** Read, Write, Admin (as needed)
|
|
224
226
|
- **Pull requests:** Read, Write
|
|
225
227
|
5. Click **"Create"**
|
|
226
|
-
6. **Copy the generated
|
|
228
|
+
6. **Copy the generated token** (you won't see it again!)
|
|
227
229
|
|
|
228
230
|
### Step 2: Authenticate
|
|
229
231
|
|
|
@@ -231,7 +233,7 @@ The CLI uses **Bitbucket App Passwords** for secure authentication. Here's how t
|
|
|
231
233
|
bb auth login
|
|
232
234
|
```
|
|
233
235
|
|
|
234
|
-
Enter your Bitbucket username and the
|
|
236
|
+
Enter your Bitbucket username and the API Token when prompted.
|
|
235
237
|
|
|
236
238
|
### Step 3: Verify
|
|
237
239
|
|
package/dist/index.js
CHANGED
|
@@ -32653,14 +32653,14 @@ var require_promisify = __commonJS((exports) => {
|
|
|
32653
32653
|
Object.defineProperty(exports, "__esModule", {
|
|
32654
32654
|
value: true
|
|
32655
32655
|
});
|
|
32656
|
-
exports.promisify =
|
|
32656
|
+
exports.promisify = promisify2;
|
|
32657
32657
|
var customArgumentsToken = "__ES6-PROMISIFY--CUSTOM-ARGUMENTS__";
|
|
32658
|
-
function
|
|
32658
|
+
function promisify2(original) {
|
|
32659
32659
|
if (typeof original !== "function") {
|
|
32660
32660
|
throw new TypeError("Argument to promisify must be a function");
|
|
32661
32661
|
}
|
|
32662
32662
|
var argumentNames = original[customArgumentsToken];
|
|
32663
|
-
var ES6Promise =
|
|
32663
|
+
var ES6Promise = promisify2.Promise || Promise;
|
|
32664
32664
|
if (typeof ES6Promise !== "function") {
|
|
32665
32665
|
throw new Error("No Promise implementation found; do you need a polyfill?");
|
|
32666
32666
|
}
|
|
@@ -32693,8 +32693,8 @@ var require_promisify = __commonJS((exports) => {
|
|
|
32693
32693
|
});
|
|
32694
32694
|
};
|
|
32695
32695
|
}
|
|
32696
|
-
|
|
32697
|
-
|
|
32696
|
+
promisify2.argumentNames = customArgumentsToken;
|
|
32697
|
+
promisify2.Promise = undefined;
|
|
32698
32698
|
});
|
|
32699
32699
|
|
|
32700
32700
|
// node_modules/mkdirp/index.js
|
|
@@ -32793,8 +32793,8 @@ var require_systemShell = __commonJS((exports, module) => {
|
|
|
32793
32793
|
var require_exists = __commonJS((exports, module) => {
|
|
32794
32794
|
var fs = __require("fs");
|
|
32795
32795
|
var untildify = require_untildify();
|
|
32796
|
-
var { promisify } = require_promisify();
|
|
32797
|
-
var readFile =
|
|
32796
|
+
var { promisify: promisify2 } = require_promisify();
|
|
32797
|
+
var readFile = promisify2(fs.readFile);
|
|
32798
32798
|
module.exports = async (file) => {
|
|
32799
32799
|
let fileExists;
|
|
32800
32800
|
try {
|
|
@@ -32841,13 +32841,13 @@ var require_installer = __commonJS((exports, module) => {
|
|
|
32841
32841
|
var fs = __require("fs");
|
|
32842
32842
|
var path = __require("path");
|
|
32843
32843
|
var untildify = require_untildify();
|
|
32844
|
-
var { promisify } = require_promisify();
|
|
32845
|
-
var mkdirp =
|
|
32844
|
+
var { promisify: promisify2 } = require_promisify();
|
|
32845
|
+
var mkdirp = promisify2(require_mkdirp());
|
|
32846
32846
|
var { tabtabDebug, systemShell, exists } = require_utils2();
|
|
32847
32847
|
var debug = tabtabDebug("tabtab:installer");
|
|
32848
|
-
var readFile =
|
|
32849
|
-
var writeFile =
|
|
32850
|
-
var unlink =
|
|
32848
|
+
var readFile = promisify2(fs.readFile);
|
|
32849
|
+
var writeFile = promisify2(fs.writeFile);
|
|
32850
|
+
var unlink = promisify2(fs.unlink);
|
|
32851
32851
|
var {
|
|
32852
32852
|
BASH_LOCATION,
|
|
32853
32853
|
FISH_LOCATION,
|
|
@@ -33262,6 +33262,7 @@ var ServiceTokens = {
|
|
|
33262
33262
|
ApprovePRCommand: "ApprovePRCommand",
|
|
33263
33263
|
DeclinePRCommand: "DeclinePRCommand",
|
|
33264
33264
|
CheckoutPRCommand: "CheckoutPRCommand",
|
|
33265
|
+
DiffPRCommand: "DiffPRCommand",
|
|
33265
33266
|
GetConfigCommand: "GetConfigCommand",
|
|
33266
33267
|
SetConfigCommand: "SetConfigCommand",
|
|
33267
33268
|
ListConfigCommand: "ListConfigCommand",
|
|
@@ -33465,14 +33466,14 @@ class ConfigService {
|
|
|
33465
33466
|
if (!configResult.success) {
|
|
33466
33467
|
return configResult;
|
|
33467
33468
|
}
|
|
33468
|
-
const { username,
|
|
33469
|
-
if (!username || !
|
|
33469
|
+
const { username, apiToken } = configResult.value;
|
|
33470
|
+
if (!username || !apiToken) {
|
|
33470
33471
|
return Result.err(new BBError({
|
|
33471
33472
|
code: 1001 /* AUTH_REQUIRED */,
|
|
33472
33473
|
message: "Authentication required. Run 'bb auth login' to authenticate."
|
|
33473
33474
|
}));
|
|
33474
33475
|
}
|
|
33475
|
-
return Result.ok({ username,
|
|
33476
|
+
return Result.ok({ username, apiToken });
|
|
33476
33477
|
}
|
|
33477
33478
|
async setCredentials(credentials) {
|
|
33478
33479
|
const configResult = await this.getConfig();
|
|
@@ -33482,7 +33483,7 @@ class ConfigService {
|
|
|
33482
33483
|
return this.setConfig({
|
|
33483
33484
|
...configResult.value,
|
|
33484
33485
|
username: credentials.username,
|
|
33485
|
-
|
|
33486
|
+
apiToken: credentials.apiToken
|
|
33486
33487
|
});
|
|
33487
33488
|
}
|
|
33488
33489
|
async clearConfig() {
|
|
@@ -34253,11 +34254,11 @@ class HttpClient {
|
|
|
34253
34254
|
if (!credentialsResult.success) {
|
|
34254
34255
|
return credentialsResult;
|
|
34255
34256
|
}
|
|
34256
|
-
const { username,
|
|
34257
|
-
const encoded = Buffer.from(`${username}:${
|
|
34257
|
+
const { username, apiToken } = credentialsResult.value;
|
|
34258
|
+
const encoded = Buffer.from(`${username}:${apiToken}`).toString("base64");
|
|
34258
34259
|
return Result.ok(`Basic ${encoded}`);
|
|
34259
34260
|
}
|
|
34260
|
-
async request(method, path, body) {
|
|
34261
|
+
async request(method, path, body, acceptText = false) {
|
|
34261
34262
|
const authResult = await this.getAuthHeader();
|
|
34262
34263
|
if (!authResult.success) {
|
|
34263
34264
|
return authResult;
|
|
@@ -34265,7 +34266,7 @@ class HttpClient {
|
|
|
34265
34266
|
const headers = {
|
|
34266
34267
|
Authorization: authResult.value,
|
|
34267
34268
|
"Content-Type": "application/json",
|
|
34268
|
-
Accept: "application/json"
|
|
34269
|
+
Accept: acceptText ? "text/plain" : "application/json"
|
|
34269
34270
|
};
|
|
34270
34271
|
const url = `${this.baseUrl}${path}`;
|
|
34271
34272
|
try {
|
|
@@ -34278,7 +34279,7 @@ class HttpClient {
|
|
|
34278
34279
|
signal: controller.signal
|
|
34279
34280
|
});
|
|
34280
34281
|
clearTimeout(timeoutId);
|
|
34281
|
-
return this.handleResponse(response);
|
|
34282
|
+
return acceptText ? this.handleTextResponse(response) : this.handleResponse(response);
|
|
34282
34283
|
} catch (error) {
|
|
34283
34284
|
if (error instanceof Error && error.name === "AbortError") {
|
|
34284
34285
|
return Result.err(new BBError({
|
|
@@ -34322,6 +34323,33 @@ class HttpClient {
|
|
|
34322
34323
|
}));
|
|
34323
34324
|
}
|
|
34324
34325
|
}
|
|
34326
|
+
async handleTextResponse(response) {
|
|
34327
|
+
if (!response.ok) {
|
|
34328
|
+
let errorBody;
|
|
34329
|
+
try {
|
|
34330
|
+
errorBody = await response.json();
|
|
34331
|
+
} catch {
|
|
34332
|
+
errorBody = await response.text();
|
|
34333
|
+
}
|
|
34334
|
+
const message = this.extractErrorMessage(errorBody, response.statusText);
|
|
34335
|
+
return Result.err(new APIError(message, response.status, errorBody, {
|
|
34336
|
+
url: response.url
|
|
34337
|
+
}));
|
|
34338
|
+
}
|
|
34339
|
+
if (response.status === 204) {
|
|
34340
|
+
return Result.ok(undefined);
|
|
34341
|
+
}
|
|
34342
|
+
try {
|
|
34343
|
+
const data = await response.text();
|
|
34344
|
+
return Result.ok(data);
|
|
34345
|
+
} catch (error) {
|
|
34346
|
+
return Result.err(new BBError({
|
|
34347
|
+
code: 2001 /* API_REQUEST_FAILED */,
|
|
34348
|
+
message: "Failed to read response text",
|
|
34349
|
+
cause: error instanceof Error ? error : undefined
|
|
34350
|
+
}));
|
|
34351
|
+
}
|
|
34352
|
+
}
|
|
34325
34353
|
extractErrorMessage(body, fallback) {
|
|
34326
34354
|
if (typeof body === "object" && body !== null) {
|
|
34327
34355
|
const obj = body;
|
|
@@ -34340,6 +34368,9 @@ class HttpClient {
|
|
|
34340
34368
|
async get(path) {
|
|
34341
34369
|
return this.request("GET", path);
|
|
34342
34370
|
}
|
|
34371
|
+
async getText(path) {
|
|
34372
|
+
return this.request("GET", path, undefined, true);
|
|
34373
|
+
}
|
|
34343
34374
|
async post(path, body) {
|
|
34344
34375
|
return this.request("POST", path, body);
|
|
34345
34376
|
}
|
|
@@ -34408,6 +34439,12 @@ class PullRequestRepository {
|
|
|
34408
34439
|
async decline(workspace, repoSlug, id) {
|
|
34409
34440
|
return this.httpClient.post(this.buildPath(workspace, repoSlug, `/${id}/decline`));
|
|
34410
34441
|
}
|
|
34442
|
+
async getDiff(workspace, repoSlug, id) {
|
|
34443
|
+
return this.httpClient.getText(this.buildPath(workspace, repoSlug, `/${id}/diff`));
|
|
34444
|
+
}
|
|
34445
|
+
async getDiffstat(workspace, repoSlug, id) {
|
|
34446
|
+
return this.httpClient.get(this.buildPath(workspace, repoSlug, `/${id}/diffstat`));
|
|
34447
|
+
}
|
|
34411
34448
|
}
|
|
34412
34449
|
// src/core/base-command.ts
|
|
34413
34450
|
class BaseCommand {
|
|
@@ -34446,7 +34483,7 @@ class LoginCommand extends BaseCommand {
|
|
|
34446
34483
|
configService;
|
|
34447
34484
|
userRepositoryFactory;
|
|
34448
34485
|
name = "login";
|
|
34449
|
-
description = "Authenticate with Bitbucket using an
|
|
34486
|
+
description = "Authenticate with Bitbucket using an API token";
|
|
34450
34487
|
constructor(configService, userRepositoryFactory, output) {
|
|
34451
34488
|
super(output);
|
|
34452
34489
|
this.configService = configService;
|
|
@@ -34454,18 +34491,18 @@ class LoginCommand extends BaseCommand {
|
|
|
34454
34491
|
}
|
|
34455
34492
|
async execute(options, context) {
|
|
34456
34493
|
const username = options.username || process.env.BB_USERNAME;
|
|
34457
|
-
const
|
|
34494
|
+
const apiToken = options.password || process.env.BB_API_TOKEN;
|
|
34458
34495
|
if (!username) {
|
|
34459
34496
|
const error = Result.err(new ValidationError("username", "Username is required. Use --username option or set BB_USERNAME environment variable."));
|
|
34460
34497
|
this.handleResult(error, context);
|
|
34461
34498
|
return error;
|
|
34462
34499
|
}
|
|
34463
|
-
if (!
|
|
34464
|
-
const error = Result.err(new ValidationError("password", "
|
|
34500
|
+
if (!apiToken) {
|
|
34501
|
+
const error = Result.err(new ValidationError("password", "API token is required. Use --password option or set BB_API_TOKEN environment variable."));
|
|
34465
34502
|
this.handleResult(error, context);
|
|
34466
34503
|
return error;
|
|
34467
34504
|
}
|
|
34468
|
-
const setResult = await this.configService.setCredentials({ username,
|
|
34505
|
+
const setResult = await this.configService.setCredentials({ username, apiToken });
|
|
34469
34506
|
if (!setResult.success) {
|
|
34470
34507
|
this.handleResult(setResult, context);
|
|
34471
34508
|
return setResult;
|
|
@@ -34520,7 +34557,7 @@ class StatusCommand extends BaseCommand {
|
|
|
34520
34557
|
return configResult;
|
|
34521
34558
|
}
|
|
34522
34559
|
const config = configResult.value;
|
|
34523
|
-
if (!config.username || !config.
|
|
34560
|
+
if (!config.username || !config.apiToken) {
|
|
34524
34561
|
const status2 = { authenticated: false };
|
|
34525
34562
|
this.handleResult(Result.ok(status2), context, () => {
|
|
34526
34563
|
this.output.info("Not logged in");
|
|
@@ -34574,8 +34611,8 @@ class TokenCommand extends BaseCommand {
|
|
|
34574
34611
|
}
|
|
34575
34612
|
return credentialsResult;
|
|
34576
34613
|
}
|
|
34577
|
-
const { username,
|
|
34578
|
-
const token = Buffer.from(`${username}:${
|
|
34614
|
+
const { username, apiToken } = credentialsResult.value;
|
|
34615
|
+
const token = Buffer.from(`${username}:${apiToken}`).toString("base64");
|
|
34579
34616
|
this.output.text(token);
|
|
34580
34617
|
return Result.ok(token);
|
|
34581
34618
|
}
|
|
@@ -35193,6 +35230,221 @@ class CheckoutPRCommand extends BaseCommand {
|
|
|
35193
35230
|
}
|
|
35194
35231
|
}
|
|
35195
35232
|
|
|
35233
|
+
// src/commands/pr/diff.command.ts
|
|
35234
|
+
import { exec } from "child_process";
|
|
35235
|
+
import { promisify } from "util";
|
|
35236
|
+
var execAsync = promisify(exec);
|
|
35237
|
+
|
|
35238
|
+
class DiffPRCommand extends BaseCommand {
|
|
35239
|
+
prRepository;
|
|
35240
|
+
contextService;
|
|
35241
|
+
gitService;
|
|
35242
|
+
name = "diff";
|
|
35243
|
+
description = "View pull request diff";
|
|
35244
|
+
constructor(prRepository, contextService, gitService, output) {
|
|
35245
|
+
super(output);
|
|
35246
|
+
this.prRepository = prRepository;
|
|
35247
|
+
this.contextService = contextService;
|
|
35248
|
+
this.gitService = gitService;
|
|
35249
|
+
}
|
|
35250
|
+
async execute(options, context) {
|
|
35251
|
+
const repoContextResult = await this.contextService.requireRepoContext({
|
|
35252
|
+
...context.globalOptions,
|
|
35253
|
+
...options
|
|
35254
|
+
});
|
|
35255
|
+
if (!repoContextResult.success) {
|
|
35256
|
+
this.handleResult(repoContextResult, context);
|
|
35257
|
+
return repoContextResult;
|
|
35258
|
+
}
|
|
35259
|
+
const { workspace, repoSlug } = repoContextResult.value;
|
|
35260
|
+
let prId;
|
|
35261
|
+
if (options.id) {
|
|
35262
|
+
prId = parseInt(options.id, 10);
|
|
35263
|
+
if (isNaN(prId)) {
|
|
35264
|
+
const error = new BBError({
|
|
35265
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
35266
|
+
message: "Invalid PR ID"
|
|
35267
|
+
});
|
|
35268
|
+
this.handleResult(Result.err(error), context);
|
|
35269
|
+
return Result.err(error);
|
|
35270
|
+
}
|
|
35271
|
+
} else {
|
|
35272
|
+
const currentBranchResult = await this.gitService.getCurrentBranch();
|
|
35273
|
+
if (!currentBranchResult.success) {
|
|
35274
|
+
const error = new BBError({
|
|
35275
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
35276
|
+
message: "No PR ID provided and could not determine current branch"
|
|
35277
|
+
});
|
|
35278
|
+
this.handleResult(Result.err(error), context);
|
|
35279
|
+
return Result.err(error);
|
|
35280
|
+
}
|
|
35281
|
+
const currentBranch = currentBranchResult.value;
|
|
35282
|
+
const prsResult = await this.prRepository.list(workspace, repoSlug, "OPEN", 100);
|
|
35283
|
+
if (!prsResult.success) {
|
|
35284
|
+
this.handleResult(prsResult, context);
|
|
35285
|
+
return prsResult;
|
|
35286
|
+
}
|
|
35287
|
+
const pr = prsResult.value.values.find((p) => p.source.branch.name === currentBranch);
|
|
35288
|
+
if (!pr) {
|
|
35289
|
+
const error = new BBError({
|
|
35290
|
+
code: 5002 /* VALIDATION_INVALID */,
|
|
35291
|
+
message: `No open pull request found for branch "${currentBranch}"`
|
|
35292
|
+
});
|
|
35293
|
+
this.handleResult(Result.err(error), context);
|
|
35294
|
+
return Result.err(error);
|
|
35295
|
+
}
|
|
35296
|
+
prId = pr.id;
|
|
35297
|
+
}
|
|
35298
|
+
if (options.web) {
|
|
35299
|
+
const prResult = await this.prRepository.get(workspace, repoSlug, prId);
|
|
35300
|
+
if (!prResult.success) {
|
|
35301
|
+
this.handleResult(prResult, context);
|
|
35302
|
+
return prResult;
|
|
35303
|
+
}
|
|
35304
|
+
const diffUrl = prResult.value.links.diff.href;
|
|
35305
|
+
const webUrl = diffUrl.replace(/api\.bitbucket\.org\/2\.0\/repositories\/(.*?)\/pullrequests\/(\d+)\/diff/, "bitbucket.org/$1/pull-requests/$2/diff");
|
|
35306
|
+
return this.openInBrowser(webUrl, context);
|
|
35307
|
+
}
|
|
35308
|
+
if (options.stat) {
|
|
35309
|
+
return this.showStat(workspace, repoSlug, prId, context);
|
|
35310
|
+
}
|
|
35311
|
+
if (options.nameOnly) {
|
|
35312
|
+
return this.showNameOnly(workspace, repoSlug, prId, context);
|
|
35313
|
+
}
|
|
35314
|
+
return this.showDiff(workspace, repoSlug, prId, options, context);
|
|
35315
|
+
}
|
|
35316
|
+
async openInBrowser(url, context) {
|
|
35317
|
+
if (context.globalOptions.json) {
|
|
35318
|
+
this.output.json({ url });
|
|
35319
|
+
return Result.ok({ diff: url });
|
|
35320
|
+
}
|
|
35321
|
+
this.output.info(`Opening ${url} in your browser...`);
|
|
35322
|
+
try {
|
|
35323
|
+
const platform = process.platform;
|
|
35324
|
+
let command;
|
|
35325
|
+
if (platform === "darwin") {
|
|
35326
|
+
command = `open "${url}"`;
|
|
35327
|
+
} else if (platform === "win32") {
|
|
35328
|
+
command = `start "" "${url}"`;
|
|
35329
|
+
} else {
|
|
35330
|
+
command = `xdg-open "${url}"`;
|
|
35331
|
+
}
|
|
35332
|
+
await execAsync(command);
|
|
35333
|
+
return Result.ok({ diff: url });
|
|
35334
|
+
} catch (error) {
|
|
35335
|
+
const bbError = new BBError({
|
|
35336
|
+
code: 3002 /* GIT_COMMAND_FAILED */,
|
|
35337
|
+
message: "Failed to open browser",
|
|
35338
|
+
cause: error instanceof Error ? error : undefined
|
|
35339
|
+
});
|
|
35340
|
+
this.handleResult(Result.err(bbError), context);
|
|
35341
|
+
return Result.err(bbError);
|
|
35342
|
+
}
|
|
35343
|
+
}
|
|
35344
|
+
async showStat(workspace, repoSlug, prId, context) {
|
|
35345
|
+
const diffstatResult = await this.prRepository.getDiffstat(workspace, repoSlug, prId);
|
|
35346
|
+
if (!diffstatResult.success) {
|
|
35347
|
+
this.handleResult(diffstatResult, context);
|
|
35348
|
+
return diffstatResult;
|
|
35349
|
+
}
|
|
35350
|
+
const diffstat = diffstatResult.value;
|
|
35351
|
+
const files = diffstat.values.map((file) => {
|
|
35352
|
+
const path = file.new?.path || file.old?.path || "unknown";
|
|
35353
|
+
return {
|
|
35354
|
+
path,
|
|
35355
|
+
additions: file.lines_added,
|
|
35356
|
+
deletions: file.lines_removed
|
|
35357
|
+
};
|
|
35358
|
+
});
|
|
35359
|
+
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
35360
|
+
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
35361
|
+
const filesChanged = files.length;
|
|
35362
|
+
const result = {
|
|
35363
|
+
stat: {
|
|
35364
|
+
filesChanged,
|
|
35365
|
+
insertions: totalAdditions,
|
|
35366
|
+
deletions: totalDeletions,
|
|
35367
|
+
files
|
|
35368
|
+
}
|
|
35369
|
+
};
|
|
35370
|
+
this.handleResult(Result.ok(result), context, () => {
|
|
35371
|
+
for (const file of files) {
|
|
35372
|
+
const additions = file.additions > 0 ? source_default.green(`+${file.additions}`) : "";
|
|
35373
|
+
const deletions = file.deletions > 0 ? source_default.red(`-${file.deletions}`) : "";
|
|
35374
|
+
const stats = [additions, deletions].filter(Boolean).join(" ");
|
|
35375
|
+
this.output.text(`${file.path} ${stats ? `| ${stats}` : ""}`);
|
|
35376
|
+
}
|
|
35377
|
+
this.output.text("");
|
|
35378
|
+
const summary = [
|
|
35379
|
+
`${filesChanged} file${filesChanged !== 1 ? "s" : ""} changed`,
|
|
35380
|
+
totalAdditions > 0 ? source_default.green(`${totalAdditions} insertion${totalAdditions !== 1 ? "s" : ""}(+)`) : null,
|
|
35381
|
+
totalDeletions > 0 ? source_default.red(`${totalDeletions} deletion${totalDeletions !== 1 ? "s" : ""}(-)`) : null
|
|
35382
|
+
].filter(Boolean).join(", ");
|
|
35383
|
+
this.output.text(summary);
|
|
35384
|
+
});
|
|
35385
|
+
return Result.ok(result);
|
|
35386
|
+
}
|
|
35387
|
+
async showNameOnly(workspace, repoSlug, prId, context) {
|
|
35388
|
+
const diffstatResult = await this.prRepository.getDiffstat(workspace, repoSlug, prId);
|
|
35389
|
+
if (!diffstatResult.success) {
|
|
35390
|
+
this.handleResult(diffstatResult, context);
|
|
35391
|
+
return diffstatResult;
|
|
35392
|
+
}
|
|
35393
|
+
const diffstat = diffstatResult.value;
|
|
35394
|
+
const fileNames = diffstat.values.map((file) => file.new?.path || file.old?.path || "unknown");
|
|
35395
|
+
const result = {
|
|
35396
|
+
diff: fileNames.join(`
|
|
35397
|
+
`)
|
|
35398
|
+
};
|
|
35399
|
+
this.handleResult(Result.ok(result), context, () => {
|
|
35400
|
+
for (const fileName of fileNames) {
|
|
35401
|
+
this.output.text(fileName);
|
|
35402
|
+
}
|
|
35403
|
+
});
|
|
35404
|
+
return Result.ok(result);
|
|
35405
|
+
}
|
|
35406
|
+
async showDiff(workspace, repoSlug, prId, options, context) {
|
|
35407
|
+
const diffResult = await this.prRepository.getDiff(workspace, repoSlug, prId);
|
|
35408
|
+
if (!diffResult.success) {
|
|
35409
|
+
this.handleResult(diffResult, context);
|
|
35410
|
+
return diffResult;
|
|
35411
|
+
}
|
|
35412
|
+
const diff = diffResult.value;
|
|
35413
|
+
const result = { diff };
|
|
35414
|
+
this.handleResult(Result.ok(result), context, () => {
|
|
35415
|
+
const shouldColorize = this.shouldColorize(options.color);
|
|
35416
|
+
const colorizedDiff = shouldColorize ? this.colorizeDiff(diff) : diff;
|
|
35417
|
+
this.output.text(colorizedDiff);
|
|
35418
|
+
});
|
|
35419
|
+
return Result.ok(result);
|
|
35420
|
+
}
|
|
35421
|
+
shouldColorize(colorOption) {
|
|
35422
|
+
if (!colorOption || colorOption === "auto") {
|
|
35423
|
+
return process.stdout.isTTY ?? false;
|
|
35424
|
+
}
|
|
35425
|
+
return colorOption === "always";
|
|
35426
|
+
}
|
|
35427
|
+
colorizeDiff(diff) {
|
|
35428
|
+
const lines = diff.split(`
|
|
35429
|
+
`);
|
|
35430
|
+
return lines.map((line) => {
|
|
35431
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
35432
|
+
return source_default.green(line);
|
|
35433
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
35434
|
+
return source_default.red(line);
|
|
35435
|
+
} else if (line.startsWith("@@")) {
|
|
35436
|
+
return source_default.cyan(line);
|
|
35437
|
+
} else if (line.startsWith("diff --git")) {
|
|
35438
|
+
return source_default.bold(line);
|
|
35439
|
+
} else if (line.startsWith("index ") || line.startsWith("---") || line.startsWith("+++")) {
|
|
35440
|
+
return source_default.dim(line);
|
|
35441
|
+
}
|
|
35442
|
+
return line;
|
|
35443
|
+
}).join(`
|
|
35444
|
+
`);
|
|
35445
|
+
}
|
|
35446
|
+
}
|
|
35447
|
+
|
|
35196
35448
|
// src/types/config.ts
|
|
35197
35449
|
var SETTABLE_CONFIG_KEYS = ["defaultWorkspace"];
|
|
35198
35450
|
var READABLE_CONFIG_KEYS = ["username", "defaultWorkspace"];
|
|
@@ -35208,7 +35460,7 @@ class GetConfigCommand extends BaseCommand {
|
|
|
35208
35460
|
configService;
|
|
35209
35461
|
name = "get";
|
|
35210
35462
|
description = "Get a configuration value";
|
|
35211
|
-
static HIDDEN_KEYS = ["
|
|
35463
|
+
static HIDDEN_KEYS = ["apiToken"];
|
|
35212
35464
|
constructor(configService, output) {
|
|
35213
35465
|
super(output);
|
|
35214
35466
|
this.configService = configService;
|
|
@@ -35253,7 +35505,7 @@ class SetConfigCommand extends BaseCommand {
|
|
|
35253
35505
|
configService;
|
|
35254
35506
|
name = "set";
|
|
35255
35507
|
description = "Set a configuration value";
|
|
35256
|
-
static PROTECTED_KEYS = ["username", "
|
|
35508
|
+
static PROTECTED_KEYS = ["username", "apiToken"];
|
|
35257
35509
|
constructor(configService, output) {
|
|
35258
35510
|
super(output);
|
|
35259
35511
|
this.configService = configService;
|
|
@@ -35309,7 +35561,7 @@ class ListConfigCommand extends BaseCommand {
|
|
|
35309
35561
|
const displayConfig = {
|
|
35310
35562
|
username: config.username || "",
|
|
35311
35563
|
defaultWorkspace: config.defaultWorkspace || "",
|
|
35312
|
-
|
|
35564
|
+
apiToken: config.apiToken ? "********" : ""
|
|
35313
35565
|
};
|
|
35314
35566
|
this.handleResult(Result.ok(displayConfig), context, (data) => {
|
|
35315
35567
|
this.output.text(source_default.dim(`Config file: ${this.configService.getConfigPath()}`));
|
|
@@ -35495,6 +35747,13 @@ function bootstrap() {
|
|
|
35495
35747
|
const output = container.resolve(ServiceTokens.OutputService);
|
|
35496
35748
|
return new CheckoutPRCommand(prRepo, contextService, gitService, output);
|
|
35497
35749
|
});
|
|
35750
|
+
container.register(ServiceTokens.DiffPRCommand, () => {
|
|
35751
|
+
const prRepo = container.resolve(ServiceTokens.PullRequestRepository);
|
|
35752
|
+
const contextService = container.resolve(ServiceTokens.ContextService);
|
|
35753
|
+
const gitService = container.resolve(ServiceTokens.GitService);
|
|
35754
|
+
const output = container.resolve(ServiceTokens.OutputService);
|
|
35755
|
+
return new DiffPRCommand(prRepo, contextService, gitService, output);
|
|
35756
|
+
});
|
|
35498
35757
|
container.register(ServiceTokens.GetConfigCommand, () => {
|
|
35499
35758
|
const configService = container.resolve(ServiceTokens.ConfigService);
|
|
35500
35759
|
const output = container.resolve(ServiceTokens.OutputService);
|
|
@@ -35545,7 +35804,7 @@ if (process.argv.includes("--get-yargs-completions") || process.env.COMP_LINE) {
|
|
|
35545
35804
|
} else if (env2.prev === "repo") {
|
|
35546
35805
|
completions.push("clone", "create", "list", "view", "delete");
|
|
35547
35806
|
} else if (env2.prev === "pr") {
|
|
35548
|
-
completions.push("create", "list", "view", "merge", "approve", "decline", "checkout");
|
|
35807
|
+
completions.push("create", "list", "view", "merge", "approve", "decline", "checkout", "diff");
|
|
35549
35808
|
} else if (env2.prev === "config") {
|
|
35550
35809
|
completions.push("get", "set", "list");
|
|
35551
35810
|
} else if (env2.prev === "completion") {
|
|
@@ -35576,7 +35835,7 @@ function withGlobalOptions(options, context) {
|
|
|
35576
35835
|
var cli = new Command;
|
|
35577
35836
|
cli.name("bb").description("A command-line interface for Bitbucket Cloud").version(pkg.version).option("--json", "Output as JSON").option("-w, --workspace <workspace>", "Specify workspace").option("-r, --repo <repo>", "Specify repository");
|
|
35578
35837
|
var authCmd = new Command("auth").description("Authenticate with Bitbucket");
|
|
35579
|
-
authCmd.command("login").description("Authenticate with Bitbucket using an
|
|
35838
|
+
authCmd.command("login").description("Authenticate with Bitbucket using an API token").option("-u, --username <username>", "Bitbucket username").option("-p, --password <password>", "Bitbucket API token").action(async (options) => {
|
|
35580
35839
|
const cmd = container.resolve(ServiceTokens.LoginCommand);
|
|
35581
35840
|
const result = await cmd.execute(options, createContext(cli));
|
|
35582
35841
|
if (!result.success) {
|
|
@@ -35703,6 +35962,14 @@ prCmd.command("checkout <id>").description("Checkout a pull request locally").ac
|
|
|
35703
35962
|
process.exit(1);
|
|
35704
35963
|
}
|
|
35705
35964
|
});
|
|
35965
|
+
prCmd.command("diff [id]").description("View pull request diff").option("--color <when>", "Colorize output (auto, always, never)", "auto").option("--name-only", "Show only names of changed files").option("--stat", "Show diffstat").option("-w, --web", "Open diff in web browser").action(async (id, options) => {
|
|
35966
|
+
const cmd = container.resolve(ServiceTokens.DiffPRCommand);
|
|
35967
|
+
const context = createContext(cli);
|
|
35968
|
+
const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35969
|
+
if (!result.success) {
|
|
35970
|
+
process.exit(1);
|
|
35971
|
+
}
|
|
35972
|
+
});
|
|
35706
35973
|
cli.addCommand(prCmd);
|
|
35707
35974
|
var configCmd = new Command("config").description("Manage configuration");
|
|
35708
35975
|
configCmd.command("get <key>").description("Get a configuration value").action(async (key) => {
|