@bragduck/cli 2.27.0 → 2.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bragduck.js +145 -32
- package/dist/bin/bragduck.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -3653,6 +3653,32 @@ var JiraService = class {
|
|
|
3653
3653
|
return null;
|
|
3654
3654
|
}
|
|
3655
3655
|
}
|
|
3656
|
+
/**
|
|
3657
|
+
* Check if a user object matches the given identifier (accountId, email, or username)
|
|
3658
|
+
*/
|
|
3659
|
+
isMatchingUser(candidate, userIdentifier) {
|
|
3660
|
+
return candidate.email === userIdentifier || candidate.emailAddress === userIdentifier || candidate.accountId === userIdentifier || candidate.username === userIdentifier || candidate.name === userIdentifier;
|
|
3661
|
+
}
|
|
3662
|
+
/**
|
|
3663
|
+
* Filter issues to only those where the user made contributions within the date range.
|
|
3664
|
+
* Excludes issues where the user's only involvement is a static role (creator/assignee)
|
|
3665
|
+
* with a date outside the scan period.
|
|
3666
|
+
*/
|
|
3667
|
+
filterIssuesByUserContribution(issues, userIdentifier, sinceDate) {
|
|
3668
|
+
const results = [];
|
|
3669
|
+
for (const issue of issues) {
|
|
3670
|
+
const userChanges = (issue.changelog?.histories || []).filter(
|
|
3671
|
+
(h) => this.isMatchingUser(h.author, userIdentifier) && new Date(h.created) >= sinceDate
|
|
3672
|
+
);
|
|
3673
|
+
const isCreatorInRange = this.isMatchingUser(issue.fields.creator, userIdentifier) && new Date(issue.fields.created) >= sinceDate;
|
|
3674
|
+
if (userChanges.length > 0 || isCreatorInRange) {
|
|
3675
|
+
results.push({ issue, userChanges });
|
|
3676
|
+
} else {
|
|
3677
|
+
logger.debug(`Excluding issue ${issue.key} - no user contributions in date range`);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return results;
|
|
3681
|
+
}
|
|
3656
3682
|
/**
|
|
3657
3683
|
* Build JQL query from options
|
|
3658
3684
|
*/
|
|
@@ -3782,7 +3808,7 @@ var JiraService = class {
|
|
|
3782
3808
|
);
|
|
3783
3809
|
break;
|
|
3784
3810
|
}
|
|
3785
|
-
const endpoint = `/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&startAt=${startAt}&maxResults=${maxResults}&fields=${fields.join(",")}`;
|
|
3811
|
+
const endpoint = `/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&startAt=${startAt}&maxResults=${maxResults}&fields=${fields.join(",")}&expand=changelog`;
|
|
3786
3812
|
try {
|
|
3787
3813
|
const response = await this.request(endpoint);
|
|
3788
3814
|
if (response.issues.length === 0) {
|
|
@@ -3808,14 +3834,7 @@ var JiraService = class {
|
|
|
3808
3834
|
break;
|
|
3809
3835
|
}
|
|
3810
3836
|
if (options.limit && allIssues.length >= options.limit) {
|
|
3811
|
-
|
|
3812
|
-
const limitedIssues = allIssues.slice(0, options.limit);
|
|
3813
|
-
const commits2 = [];
|
|
3814
|
-
for (const issue of limitedIssues) {
|
|
3815
|
-
const commit = await this.transformIssueToCommit(issue, void 0, email2 || void 0);
|
|
3816
|
-
commits2.push(commit);
|
|
3817
|
-
}
|
|
3818
|
-
return commits2;
|
|
3837
|
+
break;
|
|
3819
3838
|
}
|
|
3820
3839
|
startAt += maxResults;
|
|
3821
3840
|
} catch (error) {
|
|
@@ -3839,9 +3858,23 @@ var JiraService = class {
|
|
|
3839
3858
|
throw error;
|
|
3840
3859
|
}
|
|
3841
3860
|
}
|
|
3861
|
+
const issuesToProcess = options.limit ? allIssues.slice(0, options.limit) : allIssues;
|
|
3842
3862
|
const email = await this.getCurrentUser();
|
|
3863
|
+
const sinceDate = options.days ? new Date(Date.now() - options.days * 24 * 60 * 60 * 1e3) : void 0;
|
|
3864
|
+
if (sinceDate && email) {
|
|
3865
|
+
const filtered = this.filterIssuesByUserContribution(issuesToProcess, email, sinceDate);
|
|
3866
|
+
logger.debug(
|
|
3867
|
+
`Date-scoped filtering: ${issuesToProcess.length} issues -> ${filtered.length} with user contributions in range`
|
|
3868
|
+
);
|
|
3869
|
+
const commits2 = [];
|
|
3870
|
+
for (const { issue, userChanges } of filtered) {
|
|
3871
|
+
const commit = await this.transformIssueToCommit(issue, void 0, email, userChanges);
|
|
3872
|
+
commits2.push(commit);
|
|
3873
|
+
}
|
|
3874
|
+
return commits2;
|
|
3875
|
+
}
|
|
3843
3876
|
const commits = [];
|
|
3844
|
-
for (const issue of
|
|
3877
|
+
for (const issue of issuesToProcess) {
|
|
3845
3878
|
const commit = await this.transformIssueToCommit(issue, void 0, email || void 0);
|
|
3846
3879
|
commits.push(commit);
|
|
3847
3880
|
}
|
|
@@ -3863,7 +3896,7 @@ var JiraService = class {
|
|
|
3863
3896
|
/**
|
|
3864
3897
|
* Transform Jira issue to GitCommit format with contribution-specific data
|
|
3865
3898
|
*/
|
|
3866
|
-
async transformIssueToCommit(issue, instanceUrl, userEmail) {
|
|
3899
|
+
async transformIssueToCommit(issue, instanceUrl, userEmail, userChanges) {
|
|
3867
3900
|
let contribution = null;
|
|
3868
3901
|
if (userEmail) {
|
|
3869
3902
|
contribution = await this.determineJiraContributionType(issue, userEmail);
|
|
@@ -3905,6 +3938,8 @@ ${truncatedDesc}`;
|
|
|
3905
3938
|
date = issue.fields.created;
|
|
3906
3939
|
} else if (contribution?.type === "assigned-resolved" && issue.fields.resolutiondate) {
|
|
3907
3940
|
date = issue.fields.resolutiondate;
|
|
3941
|
+
} else if (userChanges && userChanges.length > 0) {
|
|
3942
|
+
date = userChanges[userChanges.length - 1].created;
|
|
3908
3943
|
} else {
|
|
3909
3944
|
date = issue.fields.updated;
|
|
3910
3945
|
}
|
|
@@ -4094,6 +4129,59 @@ var ConfluenceService = class {
|
|
|
4094
4129
|
return null;
|
|
4095
4130
|
}
|
|
4096
4131
|
}
|
|
4132
|
+
/**
|
|
4133
|
+
* Check if a user object matches the given identifier (accountId, email, or username)
|
|
4134
|
+
*/
|
|
4135
|
+
isMatchingUser(candidate, userIdentifier) {
|
|
4136
|
+
return candidate.email === userIdentifier || candidate.emailAddress === userIdentifier || candidate.accountId === userIdentifier || candidate.username === userIdentifier || candidate.name === userIdentifier;
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
4139
|
+
* Fetch full version history for a page
|
|
4140
|
+
*/
|
|
4141
|
+
async getPageVersionHistory(pageId) {
|
|
4142
|
+
const allVersions = [];
|
|
4143
|
+
let start = 0;
|
|
4144
|
+
const limit = 50;
|
|
4145
|
+
while (true) {
|
|
4146
|
+
const response = await this.request(
|
|
4147
|
+
`/wiki/rest/api/content/${pageId}/version?start=${start}&limit=${limit}`
|
|
4148
|
+
);
|
|
4149
|
+
allVersions.push(...response.results);
|
|
4150
|
+
if (response.size < limit) break;
|
|
4151
|
+
start += limit;
|
|
4152
|
+
}
|
|
4153
|
+
return allVersions;
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Filter pages to only those where the user made contributions within the date range.
|
|
4157
|
+
* Fetches version history per page and checks if the user has versions in range.
|
|
4158
|
+
*/
|
|
4159
|
+
async filterPagesByUserContribution(pages, userIdentifier, sinceDate) {
|
|
4160
|
+
const results = [];
|
|
4161
|
+
for (let i = 0; i < pages.length; i++) {
|
|
4162
|
+
const page = pages[i];
|
|
4163
|
+
if (i > 0) {
|
|
4164
|
+
await new Promise((resolve) => globalThis.setTimeout(resolve, 100));
|
|
4165
|
+
}
|
|
4166
|
+
try {
|
|
4167
|
+
const versions = await this.getPageVersionHistory(page.id);
|
|
4168
|
+
const userVersions = versions.filter(
|
|
4169
|
+
(v) => this.isMatchingUser(v.by, userIdentifier) && new Date(v.when) >= sinceDate
|
|
4170
|
+
);
|
|
4171
|
+
const userCommentsInRange = page.children?.comment?.results?.filter(
|
|
4172
|
+
(comment) => this.isMatchingUser(comment.version?.by || {}, userIdentifier) && new Date(comment.version?.when || 0) >= sinceDate
|
|
4173
|
+
) || [];
|
|
4174
|
+
if (userVersions.length > 0 || userCommentsInRange.length > 0) {
|
|
4175
|
+
results.push({ page, userVersions });
|
|
4176
|
+
} else {
|
|
4177
|
+
logger.debug(`Excluding page "${page.title}" - no user contributions in date range`);
|
|
4178
|
+
}
|
|
4179
|
+
} catch (error) {
|
|
4180
|
+
logger.debug(`Skipping version history for page ${page.id}: ${error}`);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
return results;
|
|
4184
|
+
}
|
|
4097
4185
|
/**
|
|
4098
4186
|
* Build CQL query from options
|
|
4099
4187
|
* Returns empty string if no filters need CQL (will use simple endpoint instead)
|
|
@@ -4129,28 +4217,31 @@ var ConfluenceService = class {
|
|
|
4129
4217
|
* Determine the type of contribution the current user made to a page
|
|
4130
4218
|
* Returns: 'created' | 'edited' | 'commented'
|
|
4131
4219
|
*/
|
|
4132
|
-
async determineContributionType(page, userEmail) {
|
|
4133
|
-
|
|
4220
|
+
async determineContributionType(page, userEmail, userVersions) {
|
|
4221
|
+
const createdByUser = page.history?.createdBy ? this.isMatchingUser(page.history.createdBy, userEmail) : false;
|
|
4222
|
+
if (createdByUser) {
|
|
4134
4223
|
return {
|
|
4135
4224
|
type: "created",
|
|
4136
4225
|
details: `Created page with ${page.version?.number || 1} version${(page.version?.number || 1) > 1 ? "s" : ""}`
|
|
4137
4226
|
};
|
|
4138
4227
|
}
|
|
4139
|
-
const hasEdits = page.version?.by
|
|
4228
|
+
const hasEdits = userVersions ? userVersions.some((v) => !v.minorEdit || v.number > 1) : page.version?.by ? this.isMatchingUser(page.version.by, userEmail) && (page.version?.number || 0) > 1 : false;
|
|
4140
4229
|
const userComments = page.children?.comment?.results?.filter(
|
|
4141
|
-
(comment) => comment.version?.by
|
|
4230
|
+
(comment) => comment.version?.by ? this.isMatchingUser(comment.version.by, userEmail) : false
|
|
4142
4231
|
) || [];
|
|
4143
4232
|
const hasComments = userComments.length > 0;
|
|
4144
4233
|
if (hasEdits && hasComments) {
|
|
4234
|
+
const editCount = userVersions?.length || 1;
|
|
4145
4235
|
return {
|
|
4146
4236
|
type: "edited",
|
|
4147
|
-
details: `Edited page (
|
|
4237
|
+
details: `Edited page (${editCount} edit${editCount > 1 ? "s" : ""}) and added ${userComments.length} comment${userComments.length > 1 ? "s" : ""}`
|
|
4148
4238
|
};
|
|
4149
4239
|
}
|
|
4150
4240
|
if (hasEdits) {
|
|
4241
|
+
const editCount = userVersions?.length || 1;
|
|
4151
4242
|
return {
|
|
4152
4243
|
type: "edited",
|
|
4153
|
-
details: `Edited page
|
|
4244
|
+
details: `Edited page (${editCount} edit${editCount > 1 ? "s" : ""})`
|
|
4154
4245
|
};
|
|
4155
4246
|
}
|
|
4156
4247
|
if (hasComments) {
|
|
@@ -4248,14 +4339,7 @@ var ConfluenceService = class {
|
|
|
4248
4339
|
break;
|
|
4249
4340
|
}
|
|
4250
4341
|
if (options.limit && allPages.length >= options.limit) {
|
|
4251
|
-
|
|
4252
|
-
const limitedPages = allPages.slice(0, options.limit);
|
|
4253
|
-
const commits2 = [];
|
|
4254
|
-
for (const page of limitedPages) {
|
|
4255
|
-
const commit = await this.transformPageToCommit(page, void 0, email2 || void 0);
|
|
4256
|
-
commits2.push(commit);
|
|
4257
|
-
}
|
|
4258
|
-
return commits2;
|
|
4342
|
+
break;
|
|
4259
4343
|
}
|
|
4260
4344
|
start += limit;
|
|
4261
4345
|
} catch (error) {
|
|
@@ -4279,9 +4363,23 @@ var ConfluenceService = class {
|
|
|
4279
4363
|
throw error;
|
|
4280
4364
|
}
|
|
4281
4365
|
}
|
|
4366
|
+
const pagesToProcess = options.limit ? allPages.slice(0, options.limit) : allPages;
|
|
4282
4367
|
const email = await this.getCurrentUser();
|
|
4368
|
+
const sinceDate = options.days ? new Date(Date.now() - options.days * 24 * 60 * 60 * 1e3) : void 0;
|
|
4369
|
+
if (sinceDate && email) {
|
|
4370
|
+
const filtered = await this.filterPagesByUserContribution(pagesToProcess, email, sinceDate);
|
|
4371
|
+
logger.debug(
|
|
4372
|
+
`Date-scoped filtering: ${pagesToProcess.length} pages -> ${filtered.length} with user contributions in range`
|
|
4373
|
+
);
|
|
4374
|
+
const commits2 = [];
|
|
4375
|
+
for (const { page, userVersions } of filtered) {
|
|
4376
|
+
const commit = await this.transformPageToCommit(page, void 0, email, userVersions);
|
|
4377
|
+
commits2.push(commit);
|
|
4378
|
+
}
|
|
4379
|
+
return commits2;
|
|
4380
|
+
}
|
|
4283
4381
|
const commits = [];
|
|
4284
|
-
for (const page of
|
|
4382
|
+
for (const page of pagesToProcess) {
|
|
4285
4383
|
const commit = await this.transformPageToCommit(page, void 0, email || void 0);
|
|
4286
4384
|
commits.push(commit);
|
|
4287
4385
|
}
|
|
@@ -4303,10 +4401,10 @@ var ConfluenceService = class {
|
|
|
4303
4401
|
/**
|
|
4304
4402
|
* Transform Confluence page to GitCommit format with contribution-specific data
|
|
4305
4403
|
*/
|
|
4306
|
-
async transformPageToCommit(page, instanceUrl, userEmail) {
|
|
4404
|
+
async transformPageToCommit(page, instanceUrl, userEmail, userVersions) {
|
|
4307
4405
|
let contribution = null;
|
|
4308
4406
|
if (userEmail) {
|
|
4309
|
-
contribution = await this.determineContributionType(page, userEmail);
|
|
4407
|
+
contribution = await this.determineContributionType(page, userEmail, userVersions);
|
|
4310
4408
|
}
|
|
4311
4409
|
let message;
|
|
4312
4410
|
let contributionPrefix = "";
|
|
@@ -4344,7 +4442,14 @@ ${contribution.details}
|
|
|
4344
4442
|
const impactScore = contribution ? this.calculateContributionImpact(contribution.type, baseSize) : baseSize;
|
|
4345
4443
|
const author = page.history?.createdBy?.displayName || page.version?.by?.displayName || "Unknown Author";
|
|
4346
4444
|
const authorEmail = page.history?.createdBy?.email || page.version?.by?.email || "unknown@example.com";
|
|
4347
|
-
|
|
4445
|
+
let date;
|
|
4446
|
+
if (contribution?.type === "created") {
|
|
4447
|
+
date = page.history?.createdDate || page.version?.when;
|
|
4448
|
+
} else if (userVersions && userVersions.length > 0) {
|
|
4449
|
+
date = userVersions[userVersions.length - 1].when;
|
|
4450
|
+
} else {
|
|
4451
|
+
date = page.version?.when || (/* @__PURE__ */ new Date()).toISOString();
|
|
4452
|
+
}
|
|
4348
4453
|
return {
|
|
4349
4454
|
sha: page.id,
|
|
4350
4455
|
message,
|
|
@@ -5917,9 +6022,17 @@ async function syncAllAuthenticatedServices(options) {
|
|
|
5917
6022
|
);
|
|
5918
6023
|
for (const result of successful) {
|
|
5919
6024
|
const serviceLabel = result.service.charAt(0).toUpperCase() + result.service.slice(1);
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
6025
|
+
if (result.created === 0 && result.skipped > 0) {
|
|
6026
|
+
logger.info(
|
|
6027
|
+
` \u2022 ${serviceLabel}: ${colors.dim(`All ${result.skipped} item${result.skipped !== 1 ? "s" : ""} already synced`)}`
|
|
6028
|
+
);
|
|
6029
|
+
} else if (result.created === 0 && result.skipped === 0) {
|
|
6030
|
+
logger.info(` \u2022 ${serviceLabel}: ${colors.dim("No items found")}`);
|
|
6031
|
+
} else {
|
|
6032
|
+
logger.info(
|
|
6033
|
+
` \u2022 ${serviceLabel}: ${result.created} brag${result.created !== 1 ? "s" : ""} created${result.skipped > 0 ? `, ${result.skipped} skipped` : ""}`
|
|
6034
|
+
);
|
|
6035
|
+
}
|
|
5923
6036
|
}
|
|
5924
6037
|
logger.log("");
|
|
5925
6038
|
}
|