@pilatos/bitbucket-cli 0.3.1 → 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 +391 -56
- 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,15 +34491,20 @@ 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."));
|
|
34497
|
+
this.handleResult(error, context);
|
|
34498
|
+
return error;
|
|
34460
34499
|
}
|
|
34461
|
-
if (!
|
|
34462
|
-
|
|
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."));
|
|
34502
|
+
this.handleResult(error, context);
|
|
34503
|
+
return error;
|
|
34463
34504
|
}
|
|
34464
|
-
const setResult = await this.configService.setCredentials({ username,
|
|
34505
|
+
const setResult = await this.configService.setCredentials({ username, apiToken });
|
|
34465
34506
|
if (!setResult.success) {
|
|
34507
|
+
this.handleResult(setResult, context);
|
|
34466
34508
|
return setResult;
|
|
34467
34509
|
}
|
|
34468
34510
|
const userRepository = this.userRepositoryFactory(this.configService);
|
|
@@ -34515,7 +34557,7 @@ class StatusCommand extends BaseCommand {
|
|
|
34515
34557
|
return configResult;
|
|
34516
34558
|
}
|
|
34517
34559
|
const config = configResult.value;
|
|
34518
|
-
if (!config.username || !config.
|
|
34560
|
+
if (!config.username || !config.apiToken) {
|
|
34519
34561
|
const status2 = { authenticated: false };
|
|
34520
34562
|
this.handleResult(Result.ok(status2), context, () => {
|
|
34521
34563
|
this.output.info("Not logged in");
|
|
@@ -34569,8 +34611,8 @@ class TokenCommand extends BaseCommand {
|
|
|
34569
34611
|
}
|
|
34570
34612
|
return credentialsResult;
|
|
34571
34613
|
}
|
|
34572
|
-
const { username,
|
|
34573
|
-
const token = Buffer.from(`${username}:${
|
|
34614
|
+
const { username, apiToken } = credentialsResult.value;
|
|
34615
|
+
const token = Buffer.from(`${username}:${apiToken}`).toString("base64");
|
|
34574
34616
|
this.output.text(token);
|
|
34575
34617
|
return Result.ok(token);
|
|
34576
34618
|
}
|
|
@@ -35188,6 +35230,221 @@ class CheckoutPRCommand extends BaseCommand {
|
|
|
35188
35230
|
}
|
|
35189
35231
|
}
|
|
35190
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
|
+
|
|
35191
35448
|
// src/types/config.ts
|
|
35192
35449
|
var SETTABLE_CONFIG_KEYS = ["defaultWorkspace"];
|
|
35193
35450
|
var READABLE_CONFIG_KEYS = ["username", "defaultWorkspace"];
|
|
@@ -35203,7 +35460,7 @@ class GetConfigCommand extends BaseCommand {
|
|
|
35203
35460
|
configService;
|
|
35204
35461
|
name = "get";
|
|
35205
35462
|
description = "Get a configuration value";
|
|
35206
|
-
static HIDDEN_KEYS = ["
|
|
35463
|
+
static HIDDEN_KEYS = ["apiToken"];
|
|
35207
35464
|
constructor(configService, output) {
|
|
35208
35465
|
super(output);
|
|
35209
35466
|
this.configService = configService;
|
|
@@ -35248,7 +35505,7 @@ class SetConfigCommand extends BaseCommand {
|
|
|
35248
35505
|
configService;
|
|
35249
35506
|
name = "set";
|
|
35250
35507
|
description = "Set a configuration value";
|
|
35251
|
-
static PROTECTED_KEYS = ["username", "
|
|
35508
|
+
static PROTECTED_KEYS = ["username", "apiToken"];
|
|
35252
35509
|
constructor(configService, output) {
|
|
35253
35510
|
super(output);
|
|
35254
35511
|
this.configService = configService;
|
|
@@ -35304,7 +35561,7 @@ class ListConfigCommand extends BaseCommand {
|
|
|
35304
35561
|
const displayConfig = {
|
|
35305
35562
|
username: config.username || "",
|
|
35306
35563
|
defaultWorkspace: config.defaultWorkspace || "",
|
|
35307
|
-
|
|
35564
|
+
apiToken: config.apiToken ? "********" : ""
|
|
35308
35565
|
};
|
|
35309
35566
|
this.handleResult(Result.ok(displayConfig), context, (data) => {
|
|
35310
35567
|
this.output.text(source_default.dim(`Config file: ${this.configService.getConfigPath()}`));
|
|
@@ -35490,6 +35747,13 @@ function bootstrap() {
|
|
|
35490
35747
|
const output = container.resolve(ServiceTokens.OutputService);
|
|
35491
35748
|
return new CheckoutPRCommand(prRepo, contextService, gitService, output);
|
|
35492
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
|
+
});
|
|
35493
35757
|
container.register(ServiceTokens.GetConfigCommand, () => {
|
|
35494
35758
|
const configService = container.resolve(ServiceTokens.ConfigService);
|
|
35495
35759
|
const output = container.resolve(ServiceTokens.OutputService);
|
|
@@ -35540,7 +35804,7 @@ if (process.argv.includes("--get-yargs-completions") || process.env.COMP_LINE) {
|
|
|
35540
35804
|
} else if (env2.prev === "repo") {
|
|
35541
35805
|
completions.push("clone", "create", "list", "view", "delete");
|
|
35542
35806
|
} else if (env2.prev === "pr") {
|
|
35543
|
-
completions.push("create", "list", "view", "merge", "approve", "decline", "checkout");
|
|
35807
|
+
completions.push("create", "list", "view", "merge", "approve", "decline", "checkout", "diff");
|
|
35544
35808
|
} else if (env2.prev === "config") {
|
|
35545
35809
|
completions.push("get", "set", "list");
|
|
35546
35810
|
} else if (env2.prev === "completion") {
|
|
@@ -35571,108 +35835,179 @@ function withGlobalOptions(options, context) {
|
|
|
35571
35835
|
var cli = new Command;
|
|
35572
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");
|
|
35573
35837
|
var authCmd = new Command("auth").description("Authenticate with Bitbucket");
|
|
35574
|
-
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) => {
|
|
35575
35839
|
const cmd = container.resolve(ServiceTokens.LoginCommand);
|
|
35576
|
-
await cmd.execute(options, createContext(cli));
|
|
35840
|
+
const result = await cmd.execute(options, createContext(cli));
|
|
35841
|
+
if (!result.success) {
|
|
35842
|
+
process.exit(1);
|
|
35843
|
+
}
|
|
35577
35844
|
});
|
|
35578
35845
|
authCmd.command("logout").description("Log out of Bitbucket").action(async () => {
|
|
35579
35846
|
const cmd = container.resolve(ServiceTokens.LogoutCommand);
|
|
35580
|
-
await cmd.execute(undefined, createContext(cli));
|
|
35847
|
+
const result = await cmd.execute(undefined, createContext(cli));
|
|
35848
|
+
if (!result.success) {
|
|
35849
|
+
process.exit(1);
|
|
35850
|
+
}
|
|
35581
35851
|
});
|
|
35582
35852
|
authCmd.command("status").description("Show authentication status").action(async () => {
|
|
35583
35853
|
const cmd = container.resolve(ServiceTokens.StatusCommand);
|
|
35584
|
-
await cmd.execute(undefined, createContext(cli));
|
|
35854
|
+
const result = await cmd.execute(undefined, createContext(cli));
|
|
35855
|
+
if (!result.success) {
|
|
35856
|
+
process.exit(1);
|
|
35857
|
+
}
|
|
35585
35858
|
});
|
|
35586
35859
|
authCmd.command("token").description("Print the current access token").action(async () => {
|
|
35587
35860
|
const cmd = container.resolve(ServiceTokens.TokenCommand);
|
|
35588
|
-
await cmd.execute(undefined, createContext(cli));
|
|
35861
|
+
const result = await cmd.execute(undefined, createContext(cli));
|
|
35862
|
+
if (!result.success) {
|
|
35863
|
+
process.exit(1);
|
|
35864
|
+
}
|
|
35589
35865
|
});
|
|
35590
35866
|
cli.addCommand(authCmd);
|
|
35591
35867
|
var repoCmd = new Command("repo").description("Manage repositories");
|
|
35592
35868
|
repoCmd.command("clone <repository>").description("Clone a Bitbucket repository").option("-d, --directory <dir>", "Directory to clone into").action(async (repository, options) => {
|
|
35593
35869
|
const cmd = container.resolve(ServiceTokens.CloneCommand);
|
|
35594
|
-
await cmd.execute({ repository, ...options }, createContext(cli));
|
|
35870
|
+
const result = await cmd.execute({ repository, ...options }, createContext(cli));
|
|
35871
|
+
if (!result.success) {
|
|
35872
|
+
process.exit(1);
|
|
35873
|
+
}
|
|
35595
35874
|
});
|
|
35596
35875
|
repoCmd.command("create <name>").description("Create a new repository").option("-d, --description <description>", "Repository description").option("--private", "Create a private repository (default)").option("--public", "Create a public repository").option("-p, --project <project>", "Project key").action(async (name, options) => {
|
|
35597
35876
|
const cmd = container.resolve(ServiceTokens.CreateRepoCommand);
|
|
35598
35877
|
const context = createContext(cli);
|
|
35599
|
-
await cmd.execute(withGlobalOptions({ name, ...options }, context), context);
|
|
35878
|
+
const result = await cmd.execute(withGlobalOptions({ name, ...options }, context), context);
|
|
35879
|
+
if (!result.success) {
|
|
35880
|
+
process.exit(1);
|
|
35881
|
+
}
|
|
35600
35882
|
});
|
|
35601
35883
|
repoCmd.command("list").description("List repositories").option("--limit <number>", "Maximum number of repositories to list", "25").action(async (options) => {
|
|
35602
35884
|
const cmd = container.resolve(ServiceTokens.ListReposCommand);
|
|
35603
35885
|
const context = createContext(cli);
|
|
35604
|
-
await cmd.execute(withGlobalOptions(options, context), context);
|
|
35886
|
+
const result = await cmd.execute(withGlobalOptions(options, context), context);
|
|
35887
|
+
if (!result.success) {
|
|
35888
|
+
process.exit(1);
|
|
35889
|
+
}
|
|
35605
35890
|
});
|
|
35606
35891
|
repoCmd.command("view [repository]").description("View repository details").action(async (repository, options) => {
|
|
35607
35892
|
const cmd = container.resolve(ServiceTokens.ViewRepoCommand);
|
|
35608
35893
|
const context = createContext(cli);
|
|
35609
|
-
await cmd.execute(withGlobalOptions({ repository, ...options }, context), context);
|
|
35894
|
+
const result = await cmd.execute(withGlobalOptions({ repository, ...options }, context), context);
|
|
35895
|
+
if (!result.success) {
|
|
35896
|
+
process.exit(1);
|
|
35897
|
+
}
|
|
35610
35898
|
});
|
|
35611
35899
|
repoCmd.command("delete <repository>").description("Delete a repository").option("-y, --yes", "Skip confirmation prompt").action(async (repository, options) => {
|
|
35612
35900
|
const cmd = container.resolve(ServiceTokens.DeleteRepoCommand);
|
|
35613
35901
|
const context = createContext(cli);
|
|
35614
|
-
await cmd.execute(withGlobalOptions({ repository, ...options }, context), context);
|
|
35902
|
+
const result = await cmd.execute(withGlobalOptions({ repository, ...options }, context), context);
|
|
35903
|
+
if (!result.success) {
|
|
35904
|
+
process.exit(1);
|
|
35905
|
+
}
|
|
35615
35906
|
});
|
|
35616
35907
|
cli.addCommand(repoCmd);
|
|
35617
35908
|
var prCmd = new Command("pr").description("Manage pull requests");
|
|
35618
35909
|
prCmd.command("create").description("Create a pull request").option("-t, --title <title>", "Pull request title").option("-b, --body <body>", "Pull request description").option("-s, --source <branch>", "Source branch (default: current branch)").option("-d, --destination <branch>", "Destination branch (default: main)").option("--close-source-branch", "Close source branch after merge").action(async (options) => {
|
|
35619
35910
|
const cmd = container.resolve(ServiceTokens.CreatePRCommand);
|
|
35620
35911
|
const context = createContext(cli);
|
|
35621
|
-
await cmd.execute(withGlobalOptions(options, context), context);
|
|
35912
|
+
const result = await cmd.execute(withGlobalOptions(options, context), context);
|
|
35913
|
+
if (!result.success) {
|
|
35914
|
+
process.exit(1);
|
|
35915
|
+
}
|
|
35622
35916
|
});
|
|
35623
35917
|
prCmd.command("list").description("List pull requests").option("-s, --state <state>", "Filter by state (OPEN, MERGED, DECLINED, SUPERSEDED)", "OPEN").option("--limit <number>", "Maximum number of PRs to list", "25").action(async (options) => {
|
|
35624
35918
|
const cmd = container.resolve(ServiceTokens.ListPRsCommand);
|
|
35625
35919
|
const context = createContext(cli);
|
|
35626
|
-
await cmd.execute(withGlobalOptions(options, context), context);
|
|
35920
|
+
const result = await cmd.execute(withGlobalOptions(options, context), context);
|
|
35921
|
+
if (!result.success) {
|
|
35922
|
+
process.exit(1);
|
|
35923
|
+
}
|
|
35627
35924
|
});
|
|
35628
35925
|
prCmd.command("view <id>").description("View pull request details").action(async (id, options) => {
|
|
35629
35926
|
const cmd = container.resolve(ServiceTokens.ViewPRCommand);
|
|
35630
35927
|
const context = createContext(cli);
|
|
35631
|
-
await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35928
|
+
const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35929
|
+
if (!result.success) {
|
|
35930
|
+
process.exit(1);
|
|
35931
|
+
}
|
|
35632
35932
|
});
|
|
35633
35933
|
prCmd.command("merge <id>").description("Merge a pull request").option("-m, --message <message>", "Merge commit message").option("--close-source-branch", "Delete the source branch after merging").option("--strategy <strategy>", "Merge strategy (merge_commit, squash, fast_forward)").action(async (id, options) => {
|
|
35634
35934
|
const cmd = container.resolve(ServiceTokens.MergePRCommand);
|
|
35635
35935
|
const context = createContext(cli);
|
|
35636
|
-
await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35936
|
+
const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35937
|
+
if (!result.success) {
|
|
35938
|
+
process.exit(1);
|
|
35939
|
+
}
|
|
35637
35940
|
});
|
|
35638
35941
|
prCmd.command("approve <id>").description("Approve a pull request").action(async (id, options) => {
|
|
35639
35942
|
const cmd = container.resolve(ServiceTokens.ApprovePRCommand);
|
|
35640
35943
|
const context = createContext(cli);
|
|
35641
|
-
await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35944
|
+
const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35945
|
+
if (!result.success) {
|
|
35946
|
+
process.exit(1);
|
|
35947
|
+
}
|
|
35642
35948
|
});
|
|
35643
35949
|
prCmd.command("decline <id>").description("Decline a pull request").action(async (id, options) => {
|
|
35644
35950
|
const cmd = container.resolve(ServiceTokens.DeclinePRCommand);
|
|
35645
35951
|
const context = createContext(cli);
|
|
35646
|
-
await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35952
|
+
const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35953
|
+
if (!result.success) {
|
|
35954
|
+
process.exit(1);
|
|
35955
|
+
}
|
|
35647
35956
|
});
|
|
35648
35957
|
prCmd.command("checkout <id>").description("Checkout a pull request locally").action(async (id, options) => {
|
|
35649
35958
|
const cmd = container.resolve(ServiceTokens.CheckoutPRCommand);
|
|
35650
35959
|
const context = createContext(cli);
|
|
35651
|
-
await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35960
|
+
const result = await cmd.execute(withGlobalOptions({ id, ...options }, context), context);
|
|
35961
|
+
if (!result.success) {
|
|
35962
|
+
process.exit(1);
|
|
35963
|
+
}
|
|
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
|
+
}
|
|
35652
35972
|
});
|
|
35653
35973
|
cli.addCommand(prCmd);
|
|
35654
35974
|
var configCmd = new Command("config").description("Manage configuration");
|
|
35655
35975
|
configCmd.command("get <key>").description("Get a configuration value").action(async (key) => {
|
|
35656
35976
|
const cmd = container.resolve(ServiceTokens.GetConfigCommand);
|
|
35657
|
-
await cmd.execute({ key }, createContext(cli));
|
|
35977
|
+
const result = await cmd.execute({ key }, createContext(cli));
|
|
35978
|
+
if (!result.success) {
|
|
35979
|
+
process.exit(1);
|
|
35980
|
+
}
|
|
35658
35981
|
});
|
|
35659
35982
|
configCmd.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
|
|
35660
35983
|
const cmd = container.resolve(ServiceTokens.SetConfigCommand);
|
|
35661
|
-
await cmd.execute({ key, value }, createContext(cli));
|
|
35984
|
+
const result = await cmd.execute({ key, value }, createContext(cli));
|
|
35985
|
+
if (!result.success) {
|
|
35986
|
+
process.exit(1);
|
|
35987
|
+
}
|
|
35662
35988
|
});
|
|
35663
35989
|
configCmd.command("list").description("List all configuration values").action(async () => {
|
|
35664
35990
|
const cmd = container.resolve(ServiceTokens.ListConfigCommand);
|
|
35665
|
-
await cmd.execute(undefined, createContext(cli));
|
|
35991
|
+
const result = await cmd.execute(undefined, createContext(cli));
|
|
35992
|
+
if (!result.success) {
|
|
35993
|
+
process.exit(1);
|
|
35994
|
+
}
|
|
35666
35995
|
});
|
|
35667
35996
|
cli.addCommand(configCmd);
|
|
35668
35997
|
var completionCmd = new Command("completion").description("Shell completion utilities");
|
|
35669
35998
|
completionCmd.command("install").description("Install shell completions for bash, zsh, or fish").action(async () => {
|
|
35670
35999
|
const cmd = container.resolve(ServiceTokens.InstallCompletionCommand);
|
|
35671
|
-
await cmd.execute(undefined, createContext(cli));
|
|
36000
|
+
const result = await cmd.execute(undefined, createContext(cli));
|
|
36001
|
+
if (!result.success) {
|
|
36002
|
+
process.exit(1);
|
|
36003
|
+
}
|
|
35672
36004
|
});
|
|
35673
36005
|
completionCmd.command("uninstall").description("Uninstall shell completions").action(async () => {
|
|
35674
36006
|
const cmd = container.resolve(ServiceTokens.UninstallCompletionCommand);
|
|
35675
|
-
await cmd.execute(undefined, createContext(cli));
|
|
36007
|
+
const result = await cmd.execute(undefined, createContext(cli));
|
|
36008
|
+
if (!result.success) {
|
|
36009
|
+
process.exit(1);
|
|
36010
|
+
}
|
|
35676
36011
|
});
|
|
35677
36012
|
cli.addCommand(completionCmd);
|
|
35678
36013
|
|