atlass 1.13.0 → 1.14.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 +17 -3
- package/dist/bitbucket.mjs +1 -1
- package/dist/{build-BblaPBOA.mjs → build-CFyTL_x2.mjs} +1330 -963
- package/dist/cli.mjs +1 -1
- package/dist/confluence.mjs +1 -1
- package/dist/jira.mjs +1 -1
- package/package.json +1 -1
|
@@ -7,25 +7,27 @@ import { spawn } from "node:child_process";
|
|
|
7
7
|
import { stripVTControlCharacters } from "node:util";
|
|
8
8
|
import { checkbox, confirm, editor, input, password, search, select } from "@inquirer/prompts";
|
|
9
9
|
import kleur from "kleur";
|
|
10
|
+
import { common, createLowlight } from "lowlight";
|
|
10
11
|
import { marked } from "marked";
|
|
11
12
|
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { common, createLowlight } from "lowlight";
|
|
13
13
|
//#region src/api/http-error.ts
|
|
14
14
|
var HttpError = class extends Error {
|
|
15
15
|
status;
|
|
16
16
|
jira;
|
|
17
|
+
path;
|
|
17
18
|
constructor(status, message, jira = {
|
|
18
19
|
errorMessages: [],
|
|
19
20
|
errors: {}
|
|
20
|
-
}) {
|
|
21
|
+
}, path = "") {
|
|
21
22
|
super(message);
|
|
22
23
|
this.status = status;
|
|
23
24
|
this.jira = jira;
|
|
25
|
+
this.path = path;
|
|
24
26
|
this.name = "HttpError";
|
|
25
27
|
}
|
|
26
28
|
};
|
|
27
29
|
function httpError(status, path, body, loginHint) {
|
|
28
|
-
if (status === 401 || status === 403) return new HttpError(status, `Authentication failed (401/403). Run \`${loginHint}\` to update your token
|
|
30
|
+
if (status === 401 || status === 403) return new HttpError(status, `Authentication failed (401/403). Run \`${loginHint}\` to update your token.`, void 0, path);
|
|
29
31
|
if (status === 404) return new HttpError(status, `Not found (404): ${path}`);
|
|
30
32
|
if (status === 409) return new HttpError(status, `Conflict (409): ${extractErrorMessage(body) || "the page changed on the server"}`);
|
|
31
33
|
if (status === 413) return new HttpError(status, "Payload too large (413): the page or an attachment exceeds the size limit.");
|
|
@@ -460,6 +462,33 @@ function parsePageId(input) {
|
|
|
460
462
|
if (fromQuery) return fromQuery[1];
|
|
461
463
|
return null;
|
|
462
464
|
}
|
|
465
|
+
const PULL_REQUEST_HOSTS = ["bitbucket.org", "www.bitbucket.org"];
|
|
466
|
+
const PULL_REQUEST_PATH = /^\/([^/]+)\/([^/]+)\/pull-requests\/(\d+)(?:\/|$)/;
|
|
467
|
+
function parsePullRequestRef(input) {
|
|
468
|
+
const trimmed = input.trim();
|
|
469
|
+
const bare = trimmed.replace(/^#/, "");
|
|
470
|
+
if (/^\d+$/.test(bare)) return { id: Number.parseInt(bare, 10) };
|
|
471
|
+
if (!/^https?:\/\//i.test(trimmed)) return null;
|
|
472
|
+
const url = parseUrl(trimmed);
|
|
473
|
+
if (!url || !PULL_REQUEST_HOSTS.includes(url.hostname.toLowerCase())) return null;
|
|
474
|
+
const match = PULL_REQUEST_PATH.exec(url.pathname);
|
|
475
|
+
if (!match) return null;
|
|
476
|
+
const [, workspace = "", repo = "", id = ""] = match;
|
|
477
|
+
return {
|
|
478
|
+
id: Number.parseInt(id, 10),
|
|
479
|
+
repo: {
|
|
480
|
+
workspace: decodeURIComponent(workspace),
|
|
481
|
+
repo: decodeURIComponent(repo)
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function parseUrl(value) {
|
|
486
|
+
try {
|
|
487
|
+
return new URL(value);
|
|
488
|
+
} catch {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
463
492
|
function isExternalHref(href) {
|
|
464
493
|
return /^[a-z][a-z0-9+.-]*:\/\//i.test(href);
|
|
465
494
|
}
|
|
@@ -659,13 +688,34 @@ async function* walkPages(client, firstPath) {
|
|
|
659
688
|
path = page.next ? pathAndQuery(page.next) : null;
|
|
660
689
|
}
|
|
661
690
|
}
|
|
662
|
-
async function
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
691
|
+
async function collectCapped(client, first, cap, map) {
|
|
692
|
+
const items = [];
|
|
693
|
+
let total;
|
|
694
|
+
let path = first;
|
|
695
|
+
while (path) {
|
|
696
|
+
const page = await client.getJson(path);
|
|
697
|
+
total ??= page.size;
|
|
698
|
+
for (const value of page.values ?? []) {
|
|
699
|
+
items.push(map(value));
|
|
700
|
+
if (items.length >= cap) return {
|
|
701
|
+
items,
|
|
702
|
+
total,
|
|
703
|
+
truncated: hasMore(page, total, cap)
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
path = page.next ? pathAndQuery(page.next) : null;
|
|
667
707
|
}
|
|
668
|
-
return
|
|
708
|
+
return {
|
|
709
|
+
items,
|
|
710
|
+
total: total ?? items.length,
|
|
711
|
+
truncated: false
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function hasMore(page, total, cap) {
|
|
715
|
+
return total === void 0 ? Boolean(page.next) : total > cap;
|
|
716
|
+
}
|
|
717
|
+
async function collectPages(client, first, limit, map) {
|
|
718
|
+
return (await collectCapped(client, first, limit, map)).items;
|
|
669
719
|
}
|
|
670
720
|
//#endregion
|
|
671
721
|
//#region src/api/bitbucket-pull-requests.ts
|
|
@@ -739,93 +789,115 @@ function toPullRequestSummary(ref, pr) {
|
|
|
739
789
|
async function listPullRequests(client, ref, params) {
|
|
740
790
|
return collectPages(client, `${repoPath(ref, "pullrequests")}?${pullRequestsQuery(params)}`, params.limit, (value) => toPullRequestSummary(ref, value));
|
|
741
791
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (state.name === "COMPLETED") return state.result?.name ?? "COMPLETED";
|
|
748
|
-
return state.name ?? "";
|
|
792
|
+
const PAGELEN = 100;
|
|
793
|
+
const RESOLUTION_FIELDS = "fields=%2Bvalues.resolution.*";
|
|
794
|
+
function pullRequestPath(ref, id, resource) {
|
|
795
|
+
const base = `${repoPath(ref, "pullrequests")}/${id}`;
|
|
796
|
+
return resource ? `${base}/${resource}` : base;
|
|
749
797
|
}
|
|
750
|
-
function
|
|
751
|
-
return
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
798
|
+
function toPullRequestDetail(ref, value) {
|
|
799
|
+
return {
|
|
800
|
+
id: value.id,
|
|
801
|
+
title: value.title ?? "",
|
|
802
|
+
description: value.description ?? "",
|
|
803
|
+
state: value.state ?? "",
|
|
804
|
+
draft: value.draft ?? false,
|
|
805
|
+
author: value.author?.display_name ?? "",
|
|
806
|
+
sourceBranch: value.source?.branch?.name ?? "",
|
|
807
|
+
destinationBranch: value.destination?.branch?.name ?? "",
|
|
808
|
+
closedBy: value.closed_by?.display_name ?? "",
|
|
809
|
+
createdOn: value.created_on ?? "",
|
|
810
|
+
updatedOn: value.updated_on ?? "",
|
|
811
|
+
url: pullRequestUrl(ref, value.id),
|
|
812
|
+
reviewers: toReviewers(value),
|
|
813
|
+
participants: toParticipants(value)
|
|
814
|
+
};
|
|
755
815
|
}
|
|
756
|
-
function
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
816
|
+
function toParticipants(value) {
|
|
817
|
+
const rows = /* @__PURE__ */ new Map();
|
|
818
|
+
const users = [value.author, ...(value.participants ?? []).map((entry) => entry.user)];
|
|
819
|
+
for (const user of users) {
|
|
820
|
+
if (!user?.account_id || !user.display_name) continue;
|
|
821
|
+
rows.set(user.account_id, {
|
|
822
|
+
accountId: user.account_id,
|
|
823
|
+
name: user.display_name
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
return [...rows.values()];
|
|
762
827
|
}
|
|
763
|
-
function
|
|
764
|
-
|
|
828
|
+
function toReviewers(value) {
|
|
829
|
+
const rows = /* @__PURE__ */ new Map();
|
|
830
|
+
for (const requested of value.reviewers ?? []) rows.set(principalKey(requested), {
|
|
831
|
+
name: requested.display_name ?? "",
|
|
832
|
+
state: ""
|
|
833
|
+
});
|
|
834
|
+
for (const participant of value.participants ?? []) {
|
|
835
|
+
const state = participantState(participant);
|
|
836
|
+
if (participant.role !== "REVIEWER" && !state) continue;
|
|
837
|
+
rows.set(principalKey(participant.user ?? {}), {
|
|
838
|
+
name: participant.user?.display_name ?? "",
|
|
839
|
+
state
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
return [...rows.values()];
|
|
765
843
|
}
|
|
766
|
-
function
|
|
767
|
-
return
|
|
844
|
+
function participantState(participant) {
|
|
845
|
+
if (participant.state) return participant.state.toUpperCase();
|
|
846
|
+
return participant.approved ? "APPROVED" : "";
|
|
768
847
|
}
|
|
769
|
-
function
|
|
770
|
-
return
|
|
771
|
-
buildNumber: p.build_number,
|
|
772
|
-
status: pipelineStatus(p.state),
|
|
773
|
-
ref: p.target?.ref_name ?? "",
|
|
774
|
-
commit: shortHash(p.target?.commit?.hash),
|
|
775
|
-
durationSeconds: wallClockSeconds(p.created_on, p.completed_on),
|
|
776
|
-
createdOn: p.created_on ?? "",
|
|
777
|
-
creator: p.creator?.display_name ?? "",
|
|
778
|
-
uuid: p.uuid,
|
|
779
|
-
url: pipelineUrl(ref, p.build_number)
|
|
780
|
-
};
|
|
848
|
+
function principalKey(user) {
|
|
849
|
+
return user.uuid ?? user.display_name ?? "";
|
|
781
850
|
}
|
|
782
|
-
function
|
|
851
|
+
function toComment(value) {
|
|
852
|
+
if (value.deleted) return null;
|
|
853
|
+
const body = value.content?.raw ?? "";
|
|
854
|
+
if (!body.trim()) return null;
|
|
783
855
|
return {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
856
|
+
id: value.id,
|
|
857
|
+
parentId: value.parent?.id ?? null,
|
|
858
|
+
author: value.user?.display_name ?? "",
|
|
859
|
+
created: value.created_on ?? "",
|
|
860
|
+
body,
|
|
861
|
+
anchor: inlineAnchor(value),
|
|
862
|
+
resolved: toResolution(value.resolution)
|
|
787
863
|
};
|
|
788
864
|
}
|
|
789
|
-
function
|
|
865
|
+
function toResolution(value) {
|
|
866
|
+
if (!value?.created_on) return null;
|
|
790
867
|
return {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
durationSeconds: wallClockSeconds(step.started_on, step.completed_on)
|
|
868
|
+
by: value.user?.display_name ?? "",
|
|
869
|
+
at: value.created_on
|
|
794
870
|
};
|
|
795
871
|
}
|
|
796
|
-
function
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return
|
|
872
|
+
function inlineAnchor(value) {
|
|
873
|
+
const path = value.inline?.path;
|
|
874
|
+
if (!path) return "";
|
|
875
|
+
const line = value.inline?.to ?? value.inline?.from;
|
|
876
|
+
return line == null ? path : `${path}:${line}`;
|
|
801
877
|
}
|
|
802
|
-
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
return
|
|
878
|
+
function toChangedFile(value) {
|
|
879
|
+
const before = value.old?.path ?? "";
|
|
880
|
+
const after = value.new?.path ?? "";
|
|
881
|
+
return {
|
|
882
|
+
path: before && after && before !== after ? `${before} -> ${after}` : after || before,
|
|
883
|
+
status: value.status ?? "",
|
|
884
|
+
added: value.lines_added ?? 0,
|
|
885
|
+
removed: value.lines_removed ?? 0
|
|
886
|
+
};
|
|
806
887
|
}
|
|
807
|
-
async function
|
|
808
|
-
|
|
809
|
-
return await client.getJson(`${pipelinesPath(ref)}/${encodeURIComponent(String(buildNumber))}`);
|
|
810
|
-
} catch (err) {
|
|
811
|
-
if (err instanceof HttpError && (err.status === 400 || err.status === 404)) return null;
|
|
812
|
-
throw err;
|
|
813
|
-
}
|
|
888
|
+
async function getPullRequest(client, ref, id) {
|
|
889
|
+
return toPullRequestDetail(ref, await client.getJson(pullRequestPath(ref, id)));
|
|
814
890
|
}
|
|
815
|
-
async function
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
return null;
|
|
891
|
+
async function listComments(client, ref, id) {
|
|
892
|
+
const page = await collectCapped(client, `${pullRequestPath(ref, id, "comments")}?pagelen=${PAGELEN}&${RESOLUTION_FIELDS}`, 200, toComment);
|
|
893
|
+
const items = page.items.filter((comment) => comment !== null).sort((a, b) => a.created.localeCompare(b.created));
|
|
894
|
+
return {
|
|
895
|
+
...page,
|
|
896
|
+
items
|
|
897
|
+
};
|
|
823
898
|
}
|
|
824
|
-
async function
|
|
825
|
-
|
|
826
|
-
const first = `${pipelinesPath(ref)}/${encodeURIComponent(pipelineId)}/steps`;
|
|
827
|
-
for await (const step of walkPages(client, first)) out.push(toStepSummary(step));
|
|
828
|
-
return out;
|
|
899
|
+
async function listChangedFiles(client, ref, id) {
|
|
900
|
+
return collectCapped(client, `${pullRequestPath(ref, id, "diffstat")}?pagelen=${PAGELEN}`, 50, toChangedFile);
|
|
829
901
|
}
|
|
830
902
|
//#endregion
|
|
831
903
|
//#region src/util/format.ts
|
|
@@ -837,10 +909,16 @@ function formatDuration(seconds) {
|
|
|
837
909
|
if (m < 60) return `${m}m${String(s).padStart(2, "0")}s`;
|
|
838
910
|
return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, "0")}m`;
|
|
839
911
|
}
|
|
912
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
913
|
+
function formatDate$1(iso) {
|
|
914
|
+
const date = new Date(iso);
|
|
915
|
+
if (Number.isNaN(date.getTime())) return iso;
|
|
916
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
917
|
+
}
|
|
840
918
|
function formatDateTime(iso) {
|
|
841
919
|
const date = new Date(iso);
|
|
842
920
|
if (Number.isNaN(date.getTime())) return iso;
|
|
843
|
-
return date.
|
|
921
|
+
return `${formatDate$1(iso)} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
844
922
|
}
|
|
845
923
|
const MINUTE = 60;
|
|
846
924
|
const HOUR = 3600;
|
|
@@ -859,134 +937,72 @@ function relativeTime(iso, nowMs) {
|
|
|
859
937
|
return `${Math.floor(days / 365)}y ago`;
|
|
860
938
|
}
|
|
861
939
|
//#endregion
|
|
862
|
-
//#region src/
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
function writeRows(term, rows, options) {
|
|
898
|
-
if (options.json) term.json(rows.map((r) => r.json));
|
|
899
|
-
else term.out(formatRows(rows, {
|
|
900
|
-
...options,
|
|
901
|
-
width: term.width
|
|
940
|
+
//#region src/commands/bitbucket-comments.ts
|
|
941
|
+
const MENTION = /@\{([^}]+)\}/g;
|
|
942
|
+
function renderedPrComments(comments, participants) {
|
|
943
|
+
const names = new Map(participants.map((person) => [person.accountId, person.name]));
|
|
944
|
+
return threadsOf(comments).map((thread) => renderThread(thread, names));
|
|
945
|
+
}
|
|
946
|
+
function threadsOf(comments) {
|
|
947
|
+
const byId = new Map(comments.map((comment) => [comment.id, comment]));
|
|
948
|
+
const threads = /* @__PURE__ */ new Map();
|
|
949
|
+
for (const comment of comments) {
|
|
950
|
+
const root = rootOf(comment, byId);
|
|
951
|
+
const thread = threads.get(root.id);
|
|
952
|
+
if (thread) thread.push(comment);
|
|
953
|
+
else threads.set(root.id, [comment]);
|
|
954
|
+
}
|
|
955
|
+
return [...threads.values()];
|
|
956
|
+
}
|
|
957
|
+
function rootOf(comment, byId) {
|
|
958
|
+
const seen = /* @__PURE__ */ new Set([comment.id]);
|
|
959
|
+
let current = comment;
|
|
960
|
+
while (current.parentId !== null) {
|
|
961
|
+
const parent = byId.get(current.parentId);
|
|
962
|
+
if (!parent || seen.has(parent.id)) break;
|
|
963
|
+
seen.add(parent.id);
|
|
964
|
+
current = parent;
|
|
965
|
+
}
|
|
966
|
+
return current;
|
|
967
|
+
}
|
|
968
|
+
function renderThread(thread, names) {
|
|
969
|
+
const rows = thread.map((comment, index) => ({
|
|
970
|
+
author: comment.author,
|
|
971
|
+
created: comment.created,
|
|
972
|
+
markdown: resolveMentions(comment.body, names),
|
|
973
|
+
...index === 0 ? { anchor: comment.anchor } : {}
|
|
902
974
|
}));
|
|
903
|
-
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (!term.interactive) throw new Error("--copy requires an interactive terminal.");
|
|
919
|
-
const selected = await term.ask.pickMany({
|
|
920
|
-
message: `Select ${noun.plural} to copy:`,
|
|
921
|
-
choices: rows.map((r) => ({
|
|
922
|
-
name: formatRow(r, term.width),
|
|
923
|
-
value: r.id
|
|
924
|
-
})),
|
|
925
|
-
pageSize: 20
|
|
975
|
+
const trailer = resolvedTrailer(thread);
|
|
976
|
+
const last = rows.at(-1);
|
|
977
|
+
if (trailer && last) last.trailer = trailer;
|
|
978
|
+
return rows;
|
|
979
|
+
}
|
|
980
|
+
function resolvedTrailer(thread) {
|
|
981
|
+
const resolved = thread.find((comment) => comment.resolved)?.resolved;
|
|
982
|
+
if (!resolved) return "";
|
|
983
|
+
const by = resolved.by ? ` by ${resolved.by}` : "";
|
|
984
|
+
return kleur.green(` ↳ resolved${by} · ${formatDateTime(resolved.at)}`);
|
|
985
|
+
}
|
|
986
|
+
function resolveMentions(body, names) {
|
|
987
|
+
return body.replace(MENTION, (raw, id) => {
|
|
988
|
+
const name = names.get(id);
|
|
989
|
+
return name ? `@${name}` : raw;
|
|
926
990
|
});
|
|
927
|
-
if (selected.length === 0) {
|
|
928
|
-
term.out("Nothing selected.");
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
let copied = 0;
|
|
932
|
-
const failures = [];
|
|
933
|
-
const queue = [...selected];
|
|
934
|
-
async function worker() {
|
|
935
|
-
for (let id = queue.shift(); id !== void 0; id = queue.shift()) try {
|
|
936
|
-
await copyOne(id);
|
|
937
|
-
copied++;
|
|
938
|
-
} catch (err) {
|
|
939
|
-
failures.push(`${id} (${err instanceof Error ? err.message : String(err)})`);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
await Promise.all(Array.from({ length: Math.min(COPY_CONCURRENCY, selected.length) }, worker));
|
|
943
|
-
const summary = `Copied ${copied} ${copied === 1 ? noun.singular : noun.plural}`;
|
|
944
|
-
if (failures.length === 0) term.out(summary);
|
|
945
|
-
else term.out(`${summary}, failed ${failures.length}: ${failures.join(", ")}`);
|
|
946
|
-
}
|
|
947
|
-
function formatRow(row, width) {
|
|
948
|
-
const room = width - stripVTControlCharacters(row.fixedColumns).length - 2;
|
|
949
|
-
const text = room > 0 ? truncate(row.freeText, room) : "";
|
|
950
|
-
const columns = hyperlink(row.id, row.url) + row.fixedColumns.slice(row.id.length);
|
|
951
|
-
return text ? `${columns} ${hyperlink(text, row.url)}` : columns;
|
|
952
|
-
}
|
|
953
|
-
function truncate(text, max) {
|
|
954
|
-
const clean = text.replace(/\s+/g, " ").trim();
|
|
955
|
-
return clean.length <= max ? clean : `${clean.slice(0, Math.max(0, max - 3))}...`;
|
|
956
991
|
}
|
|
957
992
|
//#endregion
|
|
958
|
-
//#region src/commands/bitbucket.ts
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
json: options.json,
|
|
964
|
-
empty: "No pipelines found."
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
async function bitbucketPipeline({ session, term }, arg, options) {
|
|
968
|
-
const buildNumber = parseBuildNumber(arg);
|
|
969
|
-
const ref = resolveRepo(options.repo, session);
|
|
970
|
-
const detail = await withScopeHint(PIPELINE_SCOPE, () => getPipeline(session, ref, buildNumber));
|
|
971
|
-
const steps = await withScopeHint(PIPELINE_SCOPE, () => listSteps(session, ref, detail.uuid));
|
|
972
|
-
term.out(formatPipelineDetail(detail, steps, Date.now()));
|
|
973
|
-
}
|
|
974
|
-
function pipelineRows(pipelines, nowMs) {
|
|
975
|
-
return alignedRows(pipelines, nowMs, (p) => ({
|
|
976
|
-
id: `#${p.buildNumber}`,
|
|
977
|
-
url: p.url,
|
|
978
|
-
label: p.status || "-",
|
|
979
|
-
color: colorForBitbucketState(p.status),
|
|
980
|
-
text: `${refWithCommit(p)} · ${formatDuration(p.durationSeconds)}`,
|
|
981
|
-
timestamp: p.createdOn
|
|
982
|
-
}));
|
|
983
|
-
}
|
|
993
|
+
//#region src/commands/bitbucket-shared.ts
|
|
994
|
+
const PIPELINE_SCOPE = "read:pipeline:bitbucket";
|
|
995
|
+
const PULL_REQUEST_SCOPE = "read:pullrequest:bitbucket";
|
|
996
|
+
const REPOSITORY_SCOPE = "read:repository:bitbucket";
|
|
997
|
+
const ACCOUNT_SCOPE = "read:account";
|
|
984
998
|
const STATE_COLORS = {
|
|
985
999
|
SUCCESSFUL: kleur.green,
|
|
986
1000
|
MERGED: kleur.green,
|
|
1001
|
+
APPROVED: kleur.green,
|
|
987
1002
|
FAILED: kleur.red,
|
|
988
1003
|
ERROR: kleur.red,
|
|
989
1004
|
DECLINED: kleur.red,
|
|
1005
|
+
CHANGES_REQUESTED: kleur.red,
|
|
990
1006
|
IN_PROGRESS: kleur.yellow,
|
|
991
1007
|
PENDING: kleur.yellow,
|
|
992
1008
|
OPEN: kleur.yellow,
|
|
@@ -998,867 +1014,1218 @@ const STATE_COLORS = {
|
|
|
998
1014
|
function colorForBitbucketState(state) {
|
|
999
1015
|
return STATE_COLORS[state.toUpperCase()] ?? kleur.white;
|
|
1000
1016
|
}
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
const ws = Math.max(...rows.map((r) => r.status.length));
|
|
1009
|
-
return rows.map((r) => ` ${r.name.padEnd(wn)} ${r.status.padEnd(ws)} ${r.dur}`);
|
|
1017
|
+
async function withScopeHint(scope, fn) {
|
|
1018
|
+
try {
|
|
1019
|
+
return await fn();
|
|
1020
|
+
} catch (err) {
|
|
1021
|
+
if (err instanceof HttpError && (err.status === 401 || err.status === 403)) throw new Error(`Bitbucket rejected ${err.path || "the request"} (401/403). Check the token has the ${scope} scope, or run \`atlass bitbucket login\` to update it.`);
|
|
1022
|
+
throw err;
|
|
1023
|
+
}
|
|
1010
1024
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
`Created: ${relativeTime(detail.createdOn, nowMs)}${by}`,
|
|
1020
|
-
...steps.length > 0 ? [
|
|
1021
|
-
"",
|
|
1022
|
-
"Steps:",
|
|
1023
|
-
...formatStepRows(steps)
|
|
1024
|
-
] : []
|
|
1025
|
-
];
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region src/adf/to-markdown-inline.ts
|
|
1027
|
+
function renderMedia(node, ctx) {
|
|
1028
|
+
const attrs = node.attrs ?? {};
|
|
1029
|
+
const alt = attrs.alt ?? "";
|
|
1030
|
+
const resolved = ctx.resolveMedia?.(attrs);
|
|
1031
|
+
if (resolved) return ``;
|
|
1032
|
+
return `[embedded media: ${alt || attrs.id || "media"}]`;
|
|
1026
1033
|
}
|
|
1027
|
-
function
|
|
1028
|
-
|
|
1029
|
-
return pipeline.commit ? `${pipeline.ref} (${pipeline.commit})` : pipeline.ref;
|
|
1034
|
+
function renderInline(nodes, ctx) {
|
|
1035
|
+
return nodes.map((n) => renderInlineNode(n, ctx)).join("");
|
|
1030
1036
|
}
|
|
1031
|
-
function
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1037
|
+
function renderInlineNode(node, ctx) {
|
|
1038
|
+
switch (node.type) {
|
|
1039
|
+
case "text": return applyMarks(node.text ?? "", node.marks ?? []);
|
|
1040
|
+
case "hardBreak": return " \n";
|
|
1041
|
+
case "mention": return (typeof node.attrs?.["text"] === "string" ? node.attrs["text"] : "") || "@unknown";
|
|
1042
|
+
case "emoji": {
|
|
1043
|
+
const text = node.attrs?.["text"];
|
|
1044
|
+
if (typeof text === "string" && text.length > 0) return text;
|
|
1045
|
+
const short = node.attrs?.["shortName"];
|
|
1046
|
+
return typeof short === "string" ? short : "";
|
|
1047
|
+
}
|
|
1048
|
+
case "date": return formatDate(node.attrs?.["timestamp"]);
|
|
1049
|
+
case "status": return `\`[${typeof node.attrs?.["text"] === "string" ? node.attrs["text"] : ""}]\``;
|
|
1050
|
+
case "inlineCard": {
|
|
1051
|
+
const url = node.attrs?.["url"];
|
|
1052
|
+
if (typeof url === "string") return `[${url}](${url})`;
|
|
1053
|
+
return "";
|
|
1054
|
+
}
|
|
1055
|
+
case "media": return renderMedia(node, ctx);
|
|
1056
|
+
default: return node.text ?? "";
|
|
1057
|
+
}
|
|
1035
1058
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1059
|
+
function applyMarks(text, marks) {
|
|
1060
|
+
if (text.length === 0) return text;
|
|
1061
|
+
let out = text;
|
|
1062
|
+
let href;
|
|
1063
|
+
for (const mark of marks) switch (mark.type) {
|
|
1064
|
+
case "code":
|
|
1065
|
+
out = `\`${out}\``;
|
|
1066
|
+
break;
|
|
1067
|
+
case "strong":
|
|
1068
|
+
out = `**${out}**`;
|
|
1069
|
+
break;
|
|
1070
|
+
case "em":
|
|
1071
|
+
out = `*${out}*`;
|
|
1072
|
+
break;
|
|
1073
|
+
case "strike":
|
|
1074
|
+
out = `~~${out}~~`;
|
|
1075
|
+
break;
|
|
1076
|
+
case "link": {
|
|
1077
|
+
const value = mark.attrs?.["href"];
|
|
1078
|
+
if (typeof value === "string") href = value;
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1043
1081
|
}
|
|
1082
|
+
if (href) out = `[${out}](${href})`;
|
|
1083
|
+
return out;
|
|
1084
|
+
}
|
|
1085
|
+
function formatDate(timestamp) {
|
|
1086
|
+
const ms = typeof timestamp === "string" ? Number(timestamp) : typeof timestamp === "number" ? timestamp : NaN;
|
|
1087
|
+
if (Number.isNaN(ms)) return "";
|
|
1088
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
1044
1089
|
}
|
|
1045
1090
|
//#endregion
|
|
1046
|
-
//#region src/
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
const states = pullRequestStates(options);
|
|
1052
|
-
const ref = resolveRepo(options.repo, session);
|
|
1053
|
-
const limit = parseLimit(options.limit);
|
|
1054
|
-
const query = pullRequestQuery({
|
|
1055
|
-
...await resolvePrincipals(session, profile, options),
|
|
1056
|
-
query: options.query
|
|
1057
|
-
});
|
|
1058
|
-
const prs = await withScopeHint(PULL_REQUEST_SCOPE, () => listPullRequests(session, ref, {
|
|
1059
|
-
limit,
|
|
1060
|
-
states,
|
|
1061
|
-
query
|
|
1062
|
-
}));
|
|
1063
|
-
writeRows(term, pullRequestRows(prs, Date.now()), {
|
|
1064
|
-
json: options.json,
|
|
1065
|
-
empty: emptyMessage(options),
|
|
1066
|
-
footer: prs.length === limit ? searchFooter(limit) : void 0
|
|
1067
|
-
});
|
|
1091
|
+
//#region src/adf/to-markdown.ts
|
|
1092
|
+
function adfToMarkdown(doc, options = {}) {
|
|
1093
|
+
if (!doc) return "";
|
|
1094
|
+
const ctx = { resolveMedia: options.resolveMedia };
|
|
1095
|
+
return (doc.type === "doc" ? renderBlocks(doc.content ?? [], ctx, "") : renderBlock(doc, ctx, "")).trim();
|
|
1068
1096
|
}
|
|
1069
|
-
function
|
|
1070
|
-
|
|
1071
|
-
if (options.state) throw new Error("--all cannot be combined with --state.");
|
|
1072
|
-
return allPullRequestStates();
|
|
1097
|
+
function renderBlocks(nodes, ctx, indent) {
|
|
1098
|
+
return nodes.map((n) => renderBlock(n, ctx, indent)).filter((s) => s.length > 0).join("\n\n");
|
|
1073
1099
|
}
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1100
|
+
function renderBlock(node, ctx, indent) {
|
|
1101
|
+
switch (node.type) {
|
|
1102
|
+
case "paragraph": return indent + renderInline(node.content ?? [], ctx);
|
|
1103
|
+
case "heading": {
|
|
1104
|
+
const level = clampLevel$1(node.attrs?.["level"]);
|
|
1105
|
+
return `${"#".repeat(level)} ${renderInline(node.content ?? [], ctx)}`;
|
|
1106
|
+
}
|
|
1107
|
+
case "bulletList": return renderList(node, ctx, indent, "bullet");
|
|
1108
|
+
case "orderedList": return renderList(node, ctx, indent, "ordered");
|
|
1109
|
+
case "taskList": return renderTaskList(node, ctx, indent);
|
|
1110
|
+
case "decisionList": return renderDecisionList(node, ctx, indent);
|
|
1111
|
+
case "codeBlock": return renderCodeBlock(node, indent);
|
|
1112
|
+
case "blockquote": return prefixLines(renderBlocks(node.content ?? [], ctx, ""), `${indent}> `);
|
|
1113
|
+
case "panel": return renderPanel(node, ctx, indent);
|
|
1114
|
+
case "rule": return `${indent}---`;
|
|
1115
|
+
case "table": return renderTable(node, ctx);
|
|
1116
|
+
case "mediaSingle":
|
|
1117
|
+
case "mediaGroup": return renderBlocks(node.content ?? [], ctx, indent);
|
|
1118
|
+
case "media": return indent + renderMedia(node, ctx);
|
|
1119
|
+
case "expand":
|
|
1120
|
+
case "nestedExpand": return renderExpand(node, ctx, indent);
|
|
1121
|
+
default:
|
|
1122
|
+
if (node.content?.length) return renderBlocks(node.content, ctx, indent);
|
|
1123
|
+
return node.text ? indent + node.text : "";
|
|
1124
|
+
}
|
|
1086
1125
|
}
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
return {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
};
|
|
1126
|
+
function renderList(node, ctx, indent, kind) {
|
|
1127
|
+
const start = kind === "ordered" ? toNumber(node.attrs?.["order"]) ?? 1 : 0;
|
|
1128
|
+
return (node.content ?? []).filter((n) => n.type === "listItem").map((item, i) => {
|
|
1129
|
+
return renderListItem(item, ctx, indent, kind === "ordered" ? `${start + i}. ` : "- ");
|
|
1130
|
+
}).join("\n");
|
|
1093
1131
|
}
|
|
1094
|
-
function
|
|
1095
|
-
|
|
1132
|
+
function renderListItem(item, ctx, indent, marker) {
|
|
1133
|
+
const continuationIndent = `${indent}${" ".repeat(marker.length)}`;
|
|
1134
|
+
return (item.content ?? []).map((block, i) => i === 0 ? `${indent}${marker}${renderBlock(block, ctx, "")}` : renderBlock(block, ctx, continuationIndent)).join("\n");
|
|
1096
1135
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
return uuid;
|
|
1136
|
+
function renderTaskList(node, ctx, indent) {
|
|
1137
|
+
return (node.content ?? []).filter((n) => n.type === "taskItem").map((item) => {
|
|
1138
|
+
return `${indent}- [${item.attrs?.["state"] === "DONE" ? "x" : " "}] ${renderInline(item.content ?? [], ctx)}`;
|
|
1139
|
+
}).join("\n");
|
|
1102
1140
|
}
|
|
1103
|
-
function
|
|
1104
|
-
return
|
|
1141
|
+
function renderDecisionList(node, ctx, indent) {
|
|
1142
|
+
return (node.content ?? []).filter((n) => n.type === "decisionItem").map((item) => `${indent}- (decision) ${renderInline(item.content ?? [], ctx)}`).join("\n");
|
|
1105
1143
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
bitbucket.command("logout").description("Remove stored Bitbucket credentials").action(bareAction(bitbucketLogout));
|
|
1112
|
-
bitbucket.command("status").description("Show the current Bitbucket login").action(bareAction(bitbucketStatus));
|
|
1113
|
-
bitbucket.command("pipelines").description("List recent pipeline runs for a repo").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").action(bitbucketAction(bitbucketPipelines));
|
|
1114
|
-
bitbucket.command("prs").description("List pull requests for a repo").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").option("-s, --state <state...>", "open, merged, declined, or superseded").option("--all", "include every state").option("-a, --author <who>", "limit to an author ('me', an account id, or a uuid)").option("--reviewer <who>", "limit to a reviewer ('me', an account id, or a uuid)").option("-q, --query <bbql>", "raw Bitbucket query (not for use with --author or --reviewer)").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").action(bitbucketAction(bitbucketPrs));
|
|
1115
|
-
bitbucket.command("pipeline <number>").description("Show one pipeline run and its steps").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").action(bitbucketAction(bitbucketPipeline));
|
|
1116
|
-
return bitbucket;
|
|
1144
|
+
function renderCodeBlock(node, indent) {
|
|
1145
|
+
const lang = typeof node.attrs?.["language"] === "string" ? node.attrs["language"] : "";
|
|
1146
|
+
const code = (node.content ?? []).map((n) => n.text ?? "").join("");
|
|
1147
|
+
const fence = "```";
|
|
1148
|
+
return prefixLines(`${fence}${lang}\n${code}\n${fence}`, indent);
|
|
1117
1149
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
attrs
|
|
1129
|
-
}]
|
|
1130
|
-
};
|
|
1150
|
+
const PANEL_LABELS = {
|
|
1151
|
+
info: "Info",
|
|
1152
|
+
note: "Note",
|
|
1153
|
+
warning: "Warning",
|
|
1154
|
+
success: "Success",
|
|
1155
|
+
error: "Error"
|
|
1156
|
+
};
|
|
1157
|
+
function renderPanel(node, ctx, indent) {
|
|
1158
|
+
const type = typeof node.attrs?.["panelType"] === "string" ? node.attrs["panelType"] : "info";
|
|
1159
|
+
return prefixLines(`**${PANEL_LABELS[type] ?? "Note"}**\n\n${renderBlocks(node.content ?? [], ctx, "")}`, `${indent}> `);
|
|
1131
1160
|
}
|
|
1132
|
-
function
|
|
1133
|
-
|
|
1134
|
-
marked.walkTokens(marked.lexer(md), (token) => {
|
|
1135
|
-
if (token.type === "image") hrefs.add(token.href);
|
|
1136
|
-
});
|
|
1137
|
-
return [...hrefs];
|
|
1161
|
+
function renderExpand(node, ctx, indent) {
|
|
1162
|
+
return `${indent}<details><summary>${typeof node.attrs?.["title"] === "string" ? node.attrs["title"] : "Details"}</summary>\n\n${renderBlocks(node.content ?? [], ctx, "")}\n\n${indent}</details>`;
|
|
1138
1163
|
}
|
|
1139
|
-
function
|
|
1140
|
-
const
|
|
1141
|
-
return
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1164
|
+
function renderTable(node, ctx) {
|
|
1165
|
+
const rows = (node.content ?? []).filter((n) => n.type === "tableRow");
|
|
1166
|
+
if (rows.length === 0) return "";
|
|
1167
|
+
const grid = rows.map((row) => (row.content ?? []).map((cell) => renderCellOnOneLine(cell, ctx)));
|
|
1168
|
+
const cols = Math.max(...grid.map((r) => r.length));
|
|
1169
|
+
const pad = (r) => {
|
|
1170
|
+
const copy = [...r];
|
|
1171
|
+
while (copy.length < cols) copy.push("");
|
|
1172
|
+
return copy;
|
|
1145
1173
|
};
|
|
1174
|
+
const header = pad(grid[0] ?? []);
|
|
1175
|
+
const lines = [`| ${header.join(" | ")} |`, `| ${header.map(() => "---").join(" | ")} |`];
|
|
1176
|
+
for (const row of grid.slice(1)) lines.push(`| ${pad(row).join(" | ")} |`);
|
|
1177
|
+
return lines.join("\n");
|
|
1146
1178
|
}
|
|
1147
|
-
function
|
|
1148
|
-
|
|
1149
|
-
for (const token of tokens) out.push(...block(token, ctx));
|
|
1150
|
-
return out;
|
|
1179
|
+
function renderCellOnOneLine(cell, ctx) {
|
|
1180
|
+
return renderBlocks(cell.content ?? [], ctx, "").replace(/\n+/g, " ").replace(/\|/g, "\\|").trim();
|
|
1151
1181
|
}
|
|
1152
|
-
function
|
|
1153
|
-
|
|
1154
|
-
case "space": return [];
|
|
1155
|
-
case "heading": return [{
|
|
1156
|
-
type: "heading",
|
|
1157
|
-
attrs: { level: clampLevel$1(token.depth) },
|
|
1158
|
-
content: inline(token.tokens ?? [], ctx)
|
|
1159
|
-
}];
|
|
1160
|
-
case "paragraph": return paragraphsSplitAtImages(token.tokens ?? [], ctx);
|
|
1161
|
-
case "text": return paragraphsSplitAtImages(token.tokens ?? [textToken(token.text)], ctx);
|
|
1162
|
-
case "list": return [list(token, ctx)];
|
|
1163
|
-
case "code": return [codeBlock(token)];
|
|
1164
|
-
case "blockquote": return [{
|
|
1165
|
-
type: "blockquote",
|
|
1166
|
-
content: blocks(token.tokens ?? [], ctx)
|
|
1167
|
-
}];
|
|
1168
|
-
case "table": return [table(token, ctx)];
|
|
1169
|
-
case "hr": return [{ type: "rule" }];
|
|
1170
|
-
default: return [];
|
|
1171
|
-
}
|
|
1182
|
+
function prefixLines(text, prefix) {
|
|
1183
|
+
return text.split("\n").map((line) => line.length > 0 ? prefix + line : prefix.trimEnd()).join("\n");
|
|
1172
1184
|
}
|
|
1173
|
-
function
|
|
1185
|
+
function clampLevel$1(value) {
|
|
1186
|
+
const n = toNumber(value) ?? 1;
|
|
1187
|
+
return Math.min(6, Math.max(1, n));
|
|
1188
|
+
}
|
|
1189
|
+
function toNumber(value) {
|
|
1190
|
+
return typeof value === "number" ? value : void 0;
|
|
1191
|
+
}
|
|
1192
|
+
//#endregion
|
|
1193
|
+
//#region src/markdown/highlight.ts
|
|
1194
|
+
const HEADING = /^#{1,6} /;
|
|
1195
|
+
const QUOTE = /^(\s*)>( |$)/;
|
|
1196
|
+
const LIST = /^(\s*)(-|\d+\.) (\[[ x]\] )?/;
|
|
1197
|
+
const RULE = /^\s*---$/;
|
|
1198
|
+
const TABLE_ROW = /^\|.*\|$/;
|
|
1199
|
+
const TABLE_SEPARATOR = /^\|( --- \|)+$/;
|
|
1200
|
+
const CELL_DIVIDER = /(?<!\\)\|/;
|
|
1201
|
+
const DETAILS_OPEN = /^(\s*)<details><summary>(.*)<\/summary>$/;
|
|
1202
|
+
const DETAILS_CLOSE = /^\s*<\/details>$/;
|
|
1203
|
+
const FENCE_OPEN = /^((?:\s|> |- |\d+\. )*)```(\S*)$/;
|
|
1204
|
+
const FENCE = "```";
|
|
1205
|
+
const INLINE = /(`[^`]+`)|\[([^\]]+)\]\(((?:[^()]|\([^()]*\))+)\)|\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|\*(?!\*)(.+?)(?<!\*)\*(?!\*)|~~(.+?)~~/g;
|
|
1206
|
+
const TOKEN_STYLES = {
|
|
1207
|
+
keyword: kleur.magenta,
|
|
1208
|
+
built_in: kleur.magenta,
|
|
1209
|
+
literal: kleur.magenta,
|
|
1210
|
+
string: kleur.green,
|
|
1211
|
+
regexp: kleur.green,
|
|
1212
|
+
addition: kleur.green,
|
|
1213
|
+
number: kleur.yellow,
|
|
1214
|
+
symbol: kleur.yellow,
|
|
1215
|
+
meta: kleur.yellow,
|
|
1216
|
+
attr: kleur.yellow,
|
|
1217
|
+
comment: kleur.dim,
|
|
1218
|
+
quote: kleur.dim,
|
|
1219
|
+
deletion: kleur.dim,
|
|
1220
|
+
title: kleur.cyan,
|
|
1221
|
+
type: kleur.cyan,
|
|
1222
|
+
section: kleur.cyan
|
|
1223
|
+
};
|
|
1224
|
+
const lowlight = createLowlight(common);
|
|
1225
|
+
function highlightMarkdown(md) {
|
|
1226
|
+
const lines = md.split("\n");
|
|
1174
1227
|
const out = [];
|
|
1175
|
-
let
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1228
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1229
|
+
const line = lines[i] ?? "";
|
|
1230
|
+
const fence = line.match(FENCE_OPEN);
|
|
1231
|
+
if (!fence) {
|
|
1232
|
+
out.push(highlightLine(line, lines[i + 1]));
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
const [, prefix = "", lang = ""] = fence;
|
|
1236
|
+
const styledPrefix = prefix.replace(/\S+/g, (marker) => kleur.dim(marker));
|
|
1237
|
+
out.push(`${styledPrefix}${kleur.dim(`${FENCE}${lang}`)}`);
|
|
1238
|
+
const code = [];
|
|
1239
|
+
const prefixed = [];
|
|
1240
|
+
let j = i + 1;
|
|
1241
|
+
for (; j < lines.length; j++) {
|
|
1242
|
+
const raw = lines[j] ?? "";
|
|
1243
|
+
const hasPrefix = raw.startsWith(prefix) || raw === prefix.trimEnd();
|
|
1244
|
+
const body = raw.startsWith(prefix) ? raw.slice(prefix.length) : hasPrefix ? "" : raw;
|
|
1245
|
+
if (body === FENCE) break;
|
|
1246
|
+
code.push(body);
|
|
1247
|
+
prefixed.push(hasPrefix);
|
|
1248
|
+
}
|
|
1249
|
+
if (code.length > 0) highlightCode(code.join("\n"), lang).split("\n").forEach((codeLine, k) => {
|
|
1250
|
+
const lead = prefixed[k] ? styledPrefix : "";
|
|
1251
|
+
out.push(codeLine ? `${lead}${codeLine}` : lead.trimEnd());
|
|
1182
1252
|
});
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
} else buffer.push(token);
|
|
1191
|
-
flush();
|
|
1192
|
-
return out;
|
|
1253
|
+
if (j < lines.length) {
|
|
1254
|
+
const lead = (lines[j] ?? "").startsWith(prefix) ? styledPrefix : "";
|
|
1255
|
+
out.push(`${lead}${kleur.dim(FENCE)}`);
|
|
1256
|
+
}
|
|
1257
|
+
i = j;
|
|
1258
|
+
}
|
|
1259
|
+
return out.join("\n");
|
|
1193
1260
|
}
|
|
1194
|
-
function
|
|
1195
|
-
if (
|
|
1196
|
-
|
|
1197
|
-
attrs: { localId: randomUUID() },
|
|
1198
|
-
content: token.items.map((item) => ({
|
|
1199
|
-
type: "taskItem",
|
|
1200
|
-
attrs: {
|
|
1201
|
-
localId: randomUUID(),
|
|
1202
|
-
state: item.checked ? "DONE" : "TODO"
|
|
1203
|
-
},
|
|
1204
|
-
content: inline(taskItemInline(item), ctx)
|
|
1205
|
-
}))
|
|
1206
|
-
};
|
|
1207
|
-
const node = {
|
|
1208
|
-
type: token.ordered ? "orderedList" : "bulletList",
|
|
1209
|
-
content: token.items.map((item) => listItem(item, ctx))
|
|
1210
|
-
};
|
|
1211
|
-
const start = Number(token.start);
|
|
1212
|
-
if (token.ordered && Number.isFinite(start) && start !== 1) node.attrs = { order: start };
|
|
1213
|
-
return node;
|
|
1261
|
+
function highlightCode(code, lang) {
|
|
1262
|
+
if (!lang || !lowlight.registered(lang)) return styleEachLine(code, kleur.yellow);
|
|
1263
|
+
return renderTree(lowlight.highlight(lang, code), (text) => text);
|
|
1214
1264
|
}
|
|
1215
|
-
function
|
|
1216
|
-
|
|
1265
|
+
function renderTree(node, style) {
|
|
1266
|
+
if (node.type === "text") return styleEachLine(node.value, style);
|
|
1267
|
+
if (node.type !== "root" && node.type !== "element") return "";
|
|
1268
|
+
const own = node.type === "element" ? tokenStyle(node.properties["className"]) : void 0;
|
|
1269
|
+
const next = own ? (text) => style(own(text)) : style;
|
|
1270
|
+
return node.children.map((child) => renderTree(child, next)).join("");
|
|
1271
|
+
}
|
|
1272
|
+
function tokenStyle(className) {
|
|
1273
|
+
if (!Array.isArray(className)) return void 0;
|
|
1274
|
+
for (const name of className) {
|
|
1275
|
+
if (typeof name !== "string" || !name.startsWith("hljs-")) continue;
|
|
1276
|
+
const style = TOKEN_STYLES[name.slice(5)];
|
|
1277
|
+
if (style) return style;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function styleEachLine(text, style) {
|
|
1281
|
+
return text.split("\n").map((line) => line ? style(line) : line).join("\n");
|
|
1282
|
+
}
|
|
1283
|
+
function highlightLine(line, nextLine) {
|
|
1284
|
+
const quote = line.match(QUOTE);
|
|
1285
|
+
if (quote) {
|
|
1286
|
+
const [whole, indent = ""] = quote;
|
|
1287
|
+
const rest = line.slice(whole.length);
|
|
1288
|
+
const gap = whole.endsWith(" ") ? " " : "";
|
|
1289
|
+
return `${indent}${kleur.dim(">")}${gap}${highlightLine(rest, stripPrefix(nextLine, whole))}`;
|
|
1290
|
+
}
|
|
1291
|
+
const list = line.match(LIST);
|
|
1292
|
+
if (list) {
|
|
1293
|
+
const [whole, indent = "", marker = "", checkbox] = list;
|
|
1294
|
+
const rest = line.slice(whole.length);
|
|
1295
|
+
const box = checkbox ? `${kleur.bold(checkbox.trim())} ` : "";
|
|
1296
|
+
return `${indent}${kleur.dim(marker)} ${box}${highlightLine(rest, void 0)}`;
|
|
1297
|
+
}
|
|
1298
|
+
if (HEADING.test(line)) return kleur.bold().cyan(line);
|
|
1299
|
+
if (RULE.test(line)) return kleur.dim(line);
|
|
1300
|
+
if (TABLE_SEPARATOR.test(line)) return kleur.dim(line);
|
|
1301
|
+
if (TABLE_ROW.test(line)) return tableRow(line, nextLine !== void 0 && TABLE_SEPARATOR.test(nextLine));
|
|
1302
|
+
const details = line.match(DETAILS_OPEN);
|
|
1303
|
+
if (details) {
|
|
1304
|
+
const [, indent = "", title = ""] = details;
|
|
1305
|
+
return `${indent}${kleur.dim("<details><summary>")}${highlightInline(title)}${kleur.dim("</summary>")}`;
|
|
1306
|
+
}
|
|
1307
|
+
if (DETAILS_CLOSE.test(line)) return kleur.dim(line);
|
|
1308
|
+
return highlightInline(line);
|
|
1309
|
+
}
|
|
1310
|
+
function stripPrefix(line, prefix) {
|
|
1311
|
+
return line?.startsWith(prefix) ? line.slice(prefix.length) : void 0;
|
|
1312
|
+
}
|
|
1313
|
+
function tableRow(line, header) {
|
|
1314
|
+
const styled = line.split(CELL_DIVIDER).slice(1, -1).map((cell) => header ? kleur.bold(highlightInline(cell)) : highlightInline(cell));
|
|
1315
|
+
const pipe = kleur.dim("|");
|
|
1316
|
+
return `${pipe}${styled.join(pipe)}${pipe}`;
|
|
1317
|
+
}
|
|
1318
|
+
function highlightInline(text) {
|
|
1319
|
+
return text.replace(INLINE, (match, code, linkText, url, boldItalic, bold, italic, strike) => {
|
|
1320
|
+
if (code !== void 0) return kleur.yellow(code);
|
|
1321
|
+
if (linkText !== void 0 && url !== void 0) return link(linkText, url);
|
|
1322
|
+
if (boldItalic !== void 0) return kleur.bold().italic(match);
|
|
1323
|
+
if (bold !== void 0) return kleur.bold(`**${highlightInline(bold)}**`);
|
|
1324
|
+
if (italic !== void 0) return kleur.italic(`*${highlightInline(italic)}*`);
|
|
1325
|
+
if (strike !== void 0) return kleur.strikethrough(`~~${highlightInline(strike)}~~`);
|
|
1326
|
+
return match;
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
function link(text, url) {
|
|
1330
|
+
const open = kleur.dim("[");
|
|
1331
|
+
const middle = kleur.dim("](");
|
|
1332
|
+
return `${open}${highlightInline(text)}${middle}${kleur.dim().underline(url)}${kleur.dim(")")}`;
|
|
1333
|
+
}
|
|
1334
|
+
//#endregion
|
|
1335
|
+
//#region src/commands/view.ts
|
|
1336
|
+
const INDENT = " ";
|
|
1337
|
+
function fieldLines(pairs) {
|
|
1338
|
+
const kept = pairs.filter(([, value]) => value !== "");
|
|
1339
|
+
const width = Math.max(...kept.map(([label]) => label.length)) + 1;
|
|
1340
|
+
return kept.map(([label, value]) => `${kleur.dim(`${label}:`.padEnd(width))} ${value}`);
|
|
1341
|
+
}
|
|
1342
|
+
function dateWithAge(iso, nowMs) {
|
|
1343
|
+
if (!iso) return "";
|
|
1344
|
+
return `${formatDate$1(iso)} (${relativeTime(iso, nowMs)})`;
|
|
1345
|
+
}
|
|
1346
|
+
function markdownBody(md) {
|
|
1347
|
+
return md ? ["", highlightMarkdown(md)] : [];
|
|
1348
|
+
}
|
|
1349
|
+
function bodyLines(body) {
|
|
1350
|
+
return markdownBody(adfToMarkdown(body));
|
|
1351
|
+
}
|
|
1352
|
+
function renderedComments(comments) {
|
|
1353
|
+
return comments.map((comment) => [{
|
|
1354
|
+
author: comment.author,
|
|
1355
|
+
created: comment.created,
|
|
1356
|
+
markdown: adfToMarkdown(comment.body)
|
|
1357
|
+
}]);
|
|
1358
|
+
}
|
|
1359
|
+
function commentSection(threads, options) {
|
|
1360
|
+
const total = threads.reduce((running, thread) => running + thread.length, 0);
|
|
1361
|
+
if (total === 0) return [];
|
|
1362
|
+
const visible = options.allComments ? threads : threads.slice(-5);
|
|
1363
|
+
const lines = ["", kleur.bold(commentHeading(total, threads.length, visible.length, options))];
|
|
1364
|
+
for (const thread of visible) thread.forEach((comment, index) => {
|
|
1365
|
+
lines.push("", commentHeader(comment, index > 0), ...commentBody(comment, index > 0), ...commentTrailer(comment));
|
|
1366
|
+
});
|
|
1367
|
+
return lines;
|
|
1368
|
+
}
|
|
1369
|
+
function commentHeading(total, threads, visible, options) {
|
|
1370
|
+
const count = options.truncated ? `${total}+` : String(total);
|
|
1371
|
+
if (visible === threads) return `Comments (${count})`;
|
|
1372
|
+
if (threads === total) return `Comments (${count}, showing last ${visible} — --all-comments for all)`;
|
|
1373
|
+
return `Comments (${count} in ${threads} threads, showing last ${visible} threads — --all-comments for all)`;
|
|
1374
|
+
}
|
|
1375
|
+
function commentHeader(comment, reply) {
|
|
1376
|
+
const author = kleur.bold(comment.author || "Unknown");
|
|
1377
|
+
const when = comment.created ? ` · ${kleur.dim(formatDateTime(comment.created))}` : "";
|
|
1378
|
+
const where = comment.anchor ? ` · ${kleur.dim(comment.anchor)}` : "";
|
|
1379
|
+
return `${reply ? `${INDENT}↳ ` : "─ "}${author}${when}${where}`;
|
|
1380
|
+
}
|
|
1381
|
+
function commentBody(comment, reply) {
|
|
1382
|
+
if (!comment.markdown) return [];
|
|
1383
|
+
const body = highlightMarkdown(comment.markdown);
|
|
1384
|
+
if (!reply) return [body];
|
|
1385
|
+
return [body.split("\n").map((line) => line ? `${INDENT}${line}` : line).join("\n")];
|
|
1386
|
+
}
|
|
1387
|
+
function commentTrailer(comment) {
|
|
1388
|
+
return comment.trailer ? ["", comment.trailer] : [];
|
|
1389
|
+
}
|
|
1390
|
+
function attachmentSection(attachments) {
|
|
1391
|
+
if (attachments.length === 0) return [];
|
|
1392
|
+
return [
|
|
1393
|
+
"",
|
|
1394
|
+
kleur.bold("Attachments"),
|
|
1395
|
+
...attachments.map((a) => `- ${a.filename}`)
|
|
1396
|
+
];
|
|
1397
|
+
}
|
|
1398
|
+
//#endregion
|
|
1399
|
+
//#region src/commands/bitbucket-pr.ts
|
|
1400
|
+
async function bitbucketPr({ session, term }, arg, options) {
|
|
1401
|
+
const ref = parseRef(arg);
|
|
1402
|
+
const repo = ref.repo ?? resolveRepo(options.repo, session);
|
|
1403
|
+
const detail = await withScopeHint(PULL_REQUEST_SCOPE, () => fetchDetail(session, repo, ref.id));
|
|
1404
|
+
const [comments, files] = await Promise.all([withScopeHint(PULL_REQUEST_SCOPE, () => listComments(session, repo, ref.id)), fetchFiles(session, repo, ref.id)]);
|
|
1405
|
+
if (options.json) {
|
|
1406
|
+
term.json({
|
|
1407
|
+
...detail,
|
|
1408
|
+
comments: comments.items,
|
|
1409
|
+
files: files?.items ?? null,
|
|
1410
|
+
truncated: {
|
|
1411
|
+
comments: comments.truncated,
|
|
1412
|
+
files: files?.truncated ?? false
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
const lines = formatPullRequestView(detail, comments, files, Date.now(), options.allComments);
|
|
1418
|
+
await term.page(lines.join("\n"), { pager: options.pager });
|
|
1419
|
+
}
|
|
1420
|
+
function formatPullRequestView(detail, comments, files, nowMs, allComments = false) {
|
|
1421
|
+
const label = detail.draft ? "DRAFT" : detail.state || "-";
|
|
1422
|
+
return [
|
|
1423
|
+
kleur.bold(detail.title),
|
|
1424
|
+
...fieldLines([
|
|
1425
|
+
["State", colorForBitbucketState(label)(label)],
|
|
1426
|
+
["Author", detail.author],
|
|
1427
|
+
["Branch", branchLine(detail)],
|
|
1428
|
+
["Approvals", approvalCount(detail.reviewers)],
|
|
1429
|
+
["Created", dateWithAge(detail.createdOn, nowMs)],
|
|
1430
|
+
["Updated", dateWithAge(detail.updatedOn, nowMs)],
|
|
1431
|
+
["Closed by", detail.closedBy],
|
|
1432
|
+
["URL", detail.url]
|
|
1433
|
+
]),
|
|
1434
|
+
...markdownBody(detail.description),
|
|
1435
|
+
...reviewerSection(detail.reviewers),
|
|
1436
|
+
...fileSection(files),
|
|
1437
|
+
...commentSection(renderedPrComments(comments.items, detail.participants), {
|
|
1438
|
+
allComments,
|
|
1439
|
+
truncated: comments.truncated
|
|
1440
|
+
})
|
|
1441
|
+
];
|
|
1442
|
+
}
|
|
1443
|
+
function reviewerSection(reviewers) {
|
|
1444
|
+
const heading = ["", kleur.bold("Reviewers")];
|
|
1445
|
+
if (reviewers.length === 0) return [...heading, ` ${kleur.dim("None assigned")}`];
|
|
1446
|
+
const width = Math.max(...reviewers.map((r) => r.name.length));
|
|
1447
|
+
return [...heading, ...reviewers.map((r) => ` ${r.name.padEnd(width)} ${reviewerState(r)}`)];
|
|
1448
|
+
}
|
|
1449
|
+
function fileSection(files) {
|
|
1450
|
+
if (!files) return [
|
|
1451
|
+
"",
|
|
1452
|
+
kleur.bold("Files"),
|
|
1453
|
+
` ${kleur.dim(`Unavailable: the token needs the ${REPOSITORY_SCOPE} scope.`)}`
|
|
1454
|
+
];
|
|
1455
|
+
if (files.items.length === 0) return [];
|
|
1456
|
+
const rows = files.items.map((file) => ({
|
|
1457
|
+
added: `+${file.added}`,
|
|
1458
|
+
removed: `-${file.removed}`,
|
|
1459
|
+
path: file.path
|
|
1460
|
+
}));
|
|
1461
|
+
const wa = Math.max(...rows.map((r) => r.added.length));
|
|
1462
|
+
const wr = Math.max(...rows.map((r) => r.removed.length));
|
|
1463
|
+
const total = files.total ?? files.items.length;
|
|
1464
|
+
const added = sum(files.items, (f) => f.added);
|
|
1465
|
+
const removed = sum(files.items, (f) => f.removed);
|
|
1466
|
+
return [
|
|
1467
|
+
"",
|
|
1468
|
+
kleur.bold(`Files (${total}, +${added} -${removed})`),
|
|
1469
|
+
...rows.map((r) => ` ${kleur.green(r.added.padStart(wa))} ${kleur.red(r.removed.padStart(wr))} ${r.path}`),
|
|
1470
|
+
...moreFilesLine(files)
|
|
1471
|
+
];
|
|
1472
|
+
}
|
|
1473
|
+
function moreFilesLine(files) {
|
|
1474
|
+
if (!files.truncated) return [];
|
|
1475
|
+
const rest = files.total === void 0 ? void 0 : files.total - files.items.length;
|
|
1476
|
+
return [kleur.dim(rest === void 0 ? " ... more files not shown" : ` ... and ${rest} more`)];
|
|
1477
|
+
}
|
|
1478
|
+
function sum(files, pick) {
|
|
1479
|
+
return files.reduce((running, file) => running + pick(file), 0);
|
|
1480
|
+
}
|
|
1481
|
+
function reviewerState(reviewer) {
|
|
1482
|
+
if (!reviewer.state) return kleur.dim("—");
|
|
1483
|
+
return colorForBitbucketState(reviewer.state)(reviewer.state);
|
|
1484
|
+
}
|
|
1485
|
+
function approvalCount(reviewers) {
|
|
1486
|
+
return `${reviewers.filter((r) => r.state === "APPROVED").length}/${reviewers.length}`;
|
|
1487
|
+
}
|
|
1488
|
+
function branchLine(detail) {
|
|
1489
|
+
if (!detail.sourceBranch || !detail.destinationBranch) return detail.sourceBranch || detail.destinationBranch;
|
|
1490
|
+
return `${detail.sourceBranch} → ${detail.destinationBranch}`;
|
|
1491
|
+
}
|
|
1492
|
+
function parseRef(arg) {
|
|
1493
|
+
const ref = parsePullRequestRef(arg ?? "");
|
|
1494
|
+
if (!ref) throw new Error(`Invalid pull request "${arg ?? ""}". Expected a PR number, e.g. 42, or a Bitbucket pull request URL.`);
|
|
1495
|
+
return ref;
|
|
1496
|
+
}
|
|
1497
|
+
async function fetchFiles(session, repo, id) {
|
|
1498
|
+
try {
|
|
1499
|
+
return await listChangedFiles(session, repo, id);
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
if (err instanceof HttpError && (err.status === 401 || err.status === 403)) return null;
|
|
1502
|
+
throw err;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
async function fetchDetail(session, repo, id) {
|
|
1506
|
+
try {
|
|
1507
|
+
return await getPullRequest(session, repo, id);
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
if (err instanceof HttpError && err.status === 404) throw new Error(`Pull request #${id} not found in ${repo.workspace}/${repo.repo}.`);
|
|
1510
|
+
throw err;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
//#endregion
|
|
1514
|
+
//#region src/util/link.ts
|
|
1515
|
+
const OSC8 = "\x1B]8;;";
|
|
1516
|
+
const BEL = "\x07";
|
|
1517
|
+
function hyperlink(text, url) {
|
|
1518
|
+
if (!kleur.enabled || !url) return text;
|
|
1519
|
+
return `${OSC8}${url}${BEL}${text}${OSC8}${BEL}`;
|
|
1520
|
+
}
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/commands/search-run.ts
|
|
1523
|
+
function alignedRows(items, nowMs, cells) {
|
|
1524
|
+
const rows = items.map((item) => {
|
|
1525
|
+
const cell = cells(item);
|
|
1526
|
+
return {
|
|
1527
|
+
...cell,
|
|
1528
|
+
age: relativeTime(cell.timestamp, nowMs),
|
|
1529
|
+
json: { ...item }
|
|
1530
|
+
};
|
|
1531
|
+
});
|
|
1532
|
+
const width = (pick) => Math.max(...rows.map((row) => pick(row).length));
|
|
1533
|
+
const idWidth = width((row) => row.id);
|
|
1534
|
+
const labelWidth = width((row) => row.label);
|
|
1535
|
+
const ageWidth = width((row) => row.age);
|
|
1536
|
+
return rows.map((row) => ({
|
|
1537
|
+
id: row.id,
|
|
1538
|
+
url: row.url,
|
|
1539
|
+
fixedColumns: `${row.id.padEnd(idWidth)} ${row.color(row.label.padEnd(labelWidth))} ${row.age.padEnd(ageWidth)}`,
|
|
1540
|
+
freeText: row.text,
|
|
1541
|
+
json: row.json
|
|
1542
|
+
}));
|
|
1543
|
+
}
|
|
1544
|
+
function formatRows(rows, options) {
|
|
1545
|
+
if (rows.length === 0) return [options.empty];
|
|
1546
|
+
const lines = rows.map((row) => formatRow(row, options.width));
|
|
1547
|
+
return options.footer ? [...lines, options.footer] : lines;
|
|
1548
|
+
}
|
|
1549
|
+
function writeRows(term, rows, options) {
|
|
1550
|
+
if (options.json) term.json(rows.map((r) => r.json));
|
|
1551
|
+
else term.out(formatRows(rows, {
|
|
1552
|
+
...options,
|
|
1553
|
+
width: term.width
|
|
1554
|
+
}));
|
|
1555
|
+
}
|
|
1556
|
+
const COPY_CONCURRENCY = 5;
|
|
1557
|
+
async function runSearch(term, rows, options, noun, copyOne) {
|
|
1558
|
+
if (!(options.copy && !options.json && rows.length > 0)) {
|
|
1559
|
+
writeRows(term, rows, options);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (options.out?.endsWith(".md")) throw new Error("--out must be a directory when using --copy; a .md file path would overwrite each selection.");
|
|
1563
|
+
await copySelected(term, rows, noun, copyOne);
|
|
1564
|
+
}
|
|
1565
|
+
function searchFooter(limit) {
|
|
1566
|
+
return `
|
|
1567
|
+
Showing first ${limit}; refine with flags or raise --limit.`;
|
|
1568
|
+
}
|
|
1569
|
+
async function copySelected(term, rows, noun, copyOne) {
|
|
1570
|
+
if (!term.interactive) throw new Error("--copy requires an interactive terminal.");
|
|
1571
|
+
const selected = await term.ask.pickMany({
|
|
1572
|
+
message: `Select ${noun.plural} to copy:`,
|
|
1573
|
+
choices: rows.map((r) => ({
|
|
1574
|
+
name: formatRow(r, term.width),
|
|
1575
|
+
value: r.id
|
|
1576
|
+
})),
|
|
1577
|
+
pageSize: 20
|
|
1578
|
+
});
|
|
1579
|
+
if (selected.length === 0) {
|
|
1580
|
+
term.out("Nothing selected.");
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
let copied = 0;
|
|
1584
|
+
const failures = [];
|
|
1585
|
+
const queue = [...selected];
|
|
1586
|
+
async function worker() {
|
|
1587
|
+
for (let id = queue.shift(); id !== void 0; id = queue.shift()) try {
|
|
1588
|
+
await copyOne(id);
|
|
1589
|
+
copied++;
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
failures.push(`${id} (${err instanceof Error ? err.message : String(err)})`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
await Promise.all(Array.from({ length: Math.min(COPY_CONCURRENCY, selected.length) }, worker));
|
|
1595
|
+
const summary = `Copied ${copied} ${copied === 1 ? noun.singular : noun.plural}`;
|
|
1596
|
+
if (failures.length === 0) term.out(summary);
|
|
1597
|
+
else term.out(`${summary}, failed ${failures.length}: ${failures.join(", ")}`);
|
|
1598
|
+
}
|
|
1599
|
+
function formatRow(row, width) {
|
|
1600
|
+
const room = width - stripVTControlCharacters(row.fixedColumns).length - 2;
|
|
1601
|
+
const text = room > 0 ? truncate(row.freeText, room) : "";
|
|
1602
|
+
const columns = hyperlink(row.id, row.url) + row.fixedColumns.slice(row.id.length);
|
|
1603
|
+
return text ? `${columns} ${hyperlink(text, row.url)}` : columns;
|
|
1604
|
+
}
|
|
1605
|
+
function truncate(text, max) {
|
|
1606
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
1607
|
+
return clean.length <= max ? clean : `${clean.slice(0, Math.max(0, max - 3))}...`;
|
|
1608
|
+
}
|
|
1609
|
+
//#endregion
|
|
1610
|
+
//#region src/commands/bitbucket-prs.ts
|
|
1611
|
+
async function bitbucketPrs({ session, term, profile }, options) {
|
|
1612
|
+
if (options.query && (options.author || options.reviewer)) throw new Error("--query cannot be combined with --author or --reviewer.");
|
|
1613
|
+
const states = pullRequestStates(options);
|
|
1614
|
+
const ref = resolveRepo(options.repo, session);
|
|
1615
|
+
const limit = parseLimit(options.limit);
|
|
1616
|
+
const query = pullRequestQuery({
|
|
1617
|
+
...await resolvePrincipals(session, profile, options),
|
|
1618
|
+
query: options.query
|
|
1619
|
+
});
|
|
1620
|
+
const prs = await withScopeHint(PULL_REQUEST_SCOPE, () => listPullRequests(session, ref, {
|
|
1621
|
+
limit,
|
|
1622
|
+
states,
|
|
1623
|
+
query
|
|
1624
|
+
}));
|
|
1625
|
+
writeRows(term, pullRequestRows(prs, Date.now()), {
|
|
1626
|
+
json: options.json,
|
|
1627
|
+
empty: emptyMessage(options),
|
|
1628
|
+
footer: prs.length === limit ? searchFooter(limit) : void 0
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
function pullRequestStates(options) {
|
|
1632
|
+
if (!options.all) return parsePullRequestStates(options.state);
|
|
1633
|
+
if (options.state) throw new Error("--all cannot be combined with --state.");
|
|
1634
|
+
return allPullRequestStates();
|
|
1635
|
+
}
|
|
1636
|
+
function pullRequestRows(prs, nowMs) {
|
|
1637
|
+
return alignedRows(prs, nowMs, (pr) => {
|
|
1638
|
+
const label = pr.draft ? "DRAFT" : pr.state || "-";
|
|
1639
|
+
return {
|
|
1640
|
+
id: `#${pr.id}`,
|
|
1641
|
+
url: pr.url,
|
|
1642
|
+
label,
|
|
1643
|
+
color: colorForBitbucketState(label),
|
|
1644
|
+
text: pr.title,
|
|
1645
|
+
timestamp: pr.updatedOn
|
|
1646
|
+
};
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
async function resolvePrincipals(session, profile, options) {
|
|
1650
|
+
const mine = isMe(options.author) || isMe(options.reviewer) ? await currentUuid(session, profile) : void 0;
|
|
1217
1651
|
return {
|
|
1218
|
-
|
|
1219
|
-
|
|
1652
|
+
author: isMe(options.author) ? mine : options.author,
|
|
1653
|
+
reviewer: isMe(options.reviewer) ? mine : options.reviewer
|
|
1220
1654
|
};
|
|
1221
1655
|
}
|
|
1222
|
-
function
|
|
1223
|
-
return
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1656
|
+
function isMe(value) {
|
|
1657
|
+
return value?.toLowerCase() === "me";
|
|
1658
|
+
}
|
|
1659
|
+
async function currentUuid(session, profile) {
|
|
1660
|
+
if (session.uuid) return session.uuid;
|
|
1661
|
+
const uuid = await withScopeHint(ACCOUNT_SCOPE, () => fetchCurrentUserUuid(session));
|
|
1662
|
+
await rememberBitbucketUuid(profile, uuid);
|
|
1663
|
+
return uuid;
|
|
1664
|
+
}
|
|
1665
|
+
function emptyMessage(options) {
|
|
1666
|
+
return options.author || options.reviewer || options.query || options.state || options.all ? "No matching pull requests." : "No open pull requests.";
|
|
1667
|
+
}
|
|
1668
|
+
//#endregion
|
|
1669
|
+
//#region src/api/bitbucket-pipelines.ts
|
|
1670
|
+
const BUILD_NUMBER_SCAN_LIMIT = 1e3;
|
|
1671
|
+
function pipelineStatus(state) {
|
|
1672
|
+
if (!state) return "";
|
|
1673
|
+
if (state.name === "COMPLETED") return state.result?.name ?? "COMPLETED";
|
|
1674
|
+
return state.name ?? "";
|
|
1675
|
+
}
|
|
1676
|
+
function pipelinesQuery(limit) {
|
|
1677
|
+
return new URLSearchParams({
|
|
1678
|
+
sort: "-created_on",
|
|
1679
|
+
pagelen: String(Math.min(Math.max(limit, 1), 100))
|
|
1680
|
+
}).toString();
|
|
1681
|
+
}
|
|
1682
|
+
function wallClockSeconds(startOn, endOn) {
|
|
1683
|
+
if (!startOn || !endOn) return null;
|
|
1684
|
+
const start = Date.parse(startOn);
|
|
1685
|
+
const end = Date.parse(endOn);
|
|
1686
|
+
if (Number.isNaN(start) || Number.isNaN(end)) return null;
|
|
1687
|
+
return Math.floor((end - start) / 1e3);
|
|
1688
|
+
}
|
|
1689
|
+
function shortHash(hash) {
|
|
1690
|
+
return hash?.slice(0, 7) ?? "";
|
|
1227
1691
|
}
|
|
1228
|
-
function
|
|
1229
|
-
|
|
1230
|
-
if (first && "tokens" in first && first.tokens) return first.tokens;
|
|
1231
|
-
return [textToken(item.text)];
|
|
1692
|
+
function pipelineUrl(ref, buildNumber) {
|
|
1693
|
+
return `${BITBUCKET_WEB_ORIGIN}/${ref.workspace}/${ref.repo}/pipelines/results/${buildNumber}`;
|
|
1232
1694
|
}
|
|
1233
|
-
function
|
|
1695
|
+
function toPipelineSummary(ref, p) {
|
|
1234
1696
|
return {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1697
|
+
buildNumber: p.build_number,
|
|
1698
|
+
status: pipelineStatus(p.state),
|
|
1699
|
+
ref: p.target?.ref_name ?? "",
|
|
1700
|
+
commit: shortHash(p.target?.commit?.hash),
|
|
1701
|
+
durationSeconds: wallClockSeconds(p.created_on, p.completed_on),
|
|
1702
|
+
createdOn: p.created_on ?? "",
|
|
1703
|
+
creator: p.creator?.display_name ?? "",
|
|
1704
|
+
uuid: p.uuid,
|
|
1705
|
+
url: pipelineUrl(ref, p.build_number)
|
|
1241
1706
|
};
|
|
1242
1707
|
}
|
|
1243
|
-
function
|
|
1708
|
+
function toPipelineDetail(ref, p) {
|
|
1244
1709
|
return {
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
content: token.header.map((cell) => tableCell(cell, "tableHeader", ctx))
|
|
1249
|
-
}, ...token.rows.map((row) => ({
|
|
1250
|
-
type: "tableRow",
|
|
1251
|
-
content: row.map((cell) => tableCell(cell, "tableCell", ctx))
|
|
1252
|
-
}))]
|
|
1710
|
+
...toPipelineSummary(ref, p),
|
|
1711
|
+
repo: p.repository?.full_name ?? "",
|
|
1712
|
+
trigger: p.trigger?.name ?? ""
|
|
1253
1713
|
};
|
|
1254
1714
|
}
|
|
1255
|
-
function
|
|
1715
|
+
function toStepSummary(step) {
|
|
1256
1716
|
return {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
content: inline(cell.tokens, ctx)
|
|
1261
|
-
}]
|
|
1717
|
+
name: step.name ?? "",
|
|
1718
|
+
status: pipelineStatus(step.state),
|
|
1719
|
+
durationSeconds: wallClockSeconds(step.started_on, step.completed_on)
|
|
1262
1720
|
};
|
|
1263
1721
|
}
|
|
1264
|
-
function
|
|
1265
|
-
|
|
1266
|
-
for (const token of tokens) out.push(...inlineNode(token, ctx, marks));
|
|
1267
|
-
return out;
|
|
1268
|
-
}
|
|
1269
|
-
function inlineNode(token, ctx, marks) {
|
|
1270
|
-
switch (token.type) {
|
|
1271
|
-
case "text":
|
|
1272
|
-
case "escape": {
|
|
1273
|
-
const t = token;
|
|
1274
|
-
if ("tokens" in t && t.tokens?.length) return inline(t.tokens, ctx, marks);
|
|
1275
|
-
return textNode(t.text, marks);
|
|
1276
|
-
}
|
|
1277
|
-
case "strong": return inline(token.tokens, ctx, withUniqueMark(marks, { type: "strong" }));
|
|
1278
|
-
case "em": return inline(token.tokens, ctx, withUniqueMark(marks, { type: "em" }));
|
|
1279
|
-
case "del": return inline(token.tokens, ctx, withUniqueMark(marks, { type: "strike" }));
|
|
1280
|
-
case "codespan": return textNode(token.text, withUniqueMark(marks, { type: "code" }));
|
|
1281
|
-
case "link": {
|
|
1282
|
-
const link = token;
|
|
1283
|
-
return inline(link.tokens, ctx, withUniqueMark(marks, {
|
|
1284
|
-
type: "link",
|
|
1285
|
-
attrs: { href: link.href }
|
|
1286
|
-
}));
|
|
1287
|
-
}
|
|
1288
|
-
case "br": return [{ type: "hardBreak" }];
|
|
1289
|
-
case "html": return textNode(token.text, marks);
|
|
1290
|
-
default: return "text" in token && token.text ? textNode(token.text, marks) : [];
|
|
1291
|
-
}
|
|
1722
|
+
function pipelinesPath(ref) {
|
|
1723
|
+
return repoPath(ref, "pipelines");
|
|
1292
1724
|
}
|
|
1293
|
-
function
|
|
1294
|
-
|
|
1295
|
-
const node = {
|
|
1296
|
-
type: "text",
|
|
1297
|
-
text
|
|
1298
|
-
};
|
|
1299
|
-
if (marks.length > 0) node.marks = marks;
|
|
1300
|
-
return [node];
|
|
1725
|
+
async function listPipelines(client, ref, limit) {
|
|
1726
|
+
return collectPages(client, `${pipelinesPath(ref)}?${pipelinesQuery(limit)}`, limit, (value) => toPipelineSummary(ref, value));
|
|
1301
1727
|
}
|
|
1302
|
-
function
|
|
1303
|
-
|
|
1728
|
+
async function getPipeline(client, ref, buildNumber) {
|
|
1729
|
+
const found = await fetchByBuildNumber(client, ref, buildNumber) ?? await findInRecentRuns(client, ref, buildNumber);
|
|
1730
|
+
if (!found) throw new Error(`Could not find pipeline #${buildNumber} in the ${BUILD_NUMBER_SCAN_LIMIT} most recent runs. It may be too old.`);
|
|
1731
|
+
return toPipelineDetail(ref, found);
|
|
1304
1732
|
}
|
|
1305
|
-
function
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1733
|
+
async function fetchByBuildNumber(client, ref, buildNumber) {
|
|
1734
|
+
try {
|
|
1735
|
+
return await client.getJson(`${pipelinesPath(ref)}/${encodeURIComponent(String(buildNumber))}`);
|
|
1736
|
+
} catch (err) {
|
|
1737
|
+
if (err instanceof HttpError && (err.status === 400 || err.status === 404)) return null;
|
|
1738
|
+
throw err;
|
|
1739
|
+
}
|
|
1310
1740
|
}
|
|
1311
|
-
function
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
}
|
|
1741
|
+
async function findInRecentRuns(client, ref, buildNumber) {
|
|
1742
|
+
const first = `${pipelinesPath(ref)}?${pipelinesQuery(100)}`;
|
|
1743
|
+
let scanned = 0;
|
|
1744
|
+
for await (const pipeline of walkPages(client, first)) {
|
|
1745
|
+
if (pipeline.build_number === buildNumber) return pipeline;
|
|
1746
|
+
if (++scanned >= BUILD_NUMBER_SCAN_LIMIT) break;
|
|
1747
|
+
}
|
|
1748
|
+
return null;
|
|
1318
1749
|
}
|
|
1319
|
-
function
|
|
1320
|
-
|
|
1750
|
+
async function listSteps(client, ref, pipelineId) {
|
|
1751
|
+
const out = [];
|
|
1752
|
+
const first = `${pipelinesPath(ref)}/${encodeURIComponent(pipelineId)}/steps`;
|
|
1753
|
+
for await (const step of walkPages(client, first)) out.push(toStepSummary(step));
|
|
1754
|
+
return out;
|
|
1321
1755
|
}
|
|
1322
1756
|
//#endregion
|
|
1323
|
-
//#region src/
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
})
|
|
1331
|
-
}
|
|
1332
|
-
async function uploadAttachment(client, pageId, filename, bytes) {
|
|
1333
|
-
return (await client.postMultipart(`/wiki/rest/api/content/${encodeURIComponent(pageId)}/child/attachment`, filename, bytes)).results?.[0]?.extensions?.fileId ?? await fileIdByListing(client, pageId, filename);
|
|
1757
|
+
//#region src/commands/bitbucket.ts
|
|
1758
|
+
async function bitbucketPipelines({ session, term }, options) {
|
|
1759
|
+
const ref = resolveRepo(options.repo, session);
|
|
1760
|
+
const limit = parseLimit(options.limit);
|
|
1761
|
+
writeRows(term, pipelineRows(await withScopeHint(PIPELINE_SCOPE, () => listPipelines(session, ref, limit)), Date.now()), {
|
|
1762
|
+
json: options.json,
|
|
1763
|
+
empty: "No pipelines found."
|
|
1764
|
+
});
|
|
1334
1765
|
}
|
|
1335
|
-
async function
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1766
|
+
async function bitbucketPipeline({ session, term }, arg, options) {
|
|
1767
|
+
const buildNumber = parseBuildNumber(arg);
|
|
1768
|
+
const ref = resolveRepo(options.repo, session);
|
|
1769
|
+
const detail = await withScopeHint(PIPELINE_SCOPE, () => getPipeline(session, ref, buildNumber));
|
|
1770
|
+
const steps = await withScopeHint(PIPELINE_SCOPE, () => listSteps(session, ref, detail.uuid));
|
|
1771
|
+
term.out(formatPipelineDetail(detail, steps, Date.now()));
|
|
1339
1772
|
}
|
|
1340
|
-
|
|
1341
|
-
return (
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1773
|
+
function pipelineRows(pipelines, nowMs) {
|
|
1774
|
+
return alignedRows(pipelines, nowMs, (p) => ({
|
|
1775
|
+
id: `#${p.buildNumber}`,
|
|
1776
|
+
url: p.url,
|
|
1777
|
+
label: p.status || "-",
|
|
1778
|
+
color: colorForBitbucketState(p.status),
|
|
1779
|
+
text: `${refWithCommit(p)} · ${formatDuration(p.durationSeconds)}`,
|
|
1780
|
+
timestamp: p.createdOn
|
|
1345
1781
|
}));
|
|
1346
1782
|
}
|
|
1347
|
-
function
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
async function fetchPage(client, site, id) {
|
|
1357
|
-
const page = await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}?body-format=atlas_doc_format`);
|
|
1358
|
-
const names = new UserNames(client);
|
|
1359
|
-
const [spaceKey, attachments, comments, author] = await Promise.all([
|
|
1360
|
-
fetchSpaceKey(client, page.spaceId),
|
|
1361
|
-
fetchAttachments(client, id),
|
|
1362
|
-
fetchComments$1(client, id, names),
|
|
1363
|
-
names.resolve(page.version?.authorId ?? page.authorId)
|
|
1364
|
-
]);
|
|
1365
|
-
const webui = page._links?.webui ?? "";
|
|
1366
|
-
return {
|
|
1367
|
-
id: page.id,
|
|
1368
|
-
title: page.title,
|
|
1369
|
-
spaceKey,
|
|
1370
|
-
version: page.version?.number ?? 0,
|
|
1371
|
-
author,
|
|
1372
|
-
createdAt: page.createdAt ?? "",
|
|
1373
|
-
updatedAt: page.version?.createdAt ?? "",
|
|
1374
|
-
url: webui ? `${site}/wiki${webui}` : "",
|
|
1375
|
-
body: parseAdfJson(page.body?.atlas_doc_format?.value),
|
|
1376
|
-
attachments,
|
|
1377
|
-
comments
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1380
|
-
async function fetchPageState(client, id) {
|
|
1381
|
-
const page = await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}?body-format=atlas_doc_format`);
|
|
1382
|
-
return {
|
|
1383
|
-
version: page.version?.number ?? 0,
|
|
1384
|
-
title: page.title,
|
|
1385
|
-
body: parseAdfJson(page.body?.atlas_doc_format?.value)
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1388
|
-
async function updatePage(client, id, params) {
|
|
1389
|
-
return (await client.putJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}`, {
|
|
1390
|
-
id,
|
|
1391
|
-
status: "current",
|
|
1392
|
-
title: params.title,
|
|
1393
|
-
body: {
|
|
1394
|
-
representation: "atlas_doc_format",
|
|
1395
|
-
value: JSON.stringify(params.body)
|
|
1396
|
-
},
|
|
1397
|
-
version: {
|
|
1398
|
-
number: params.nextVersion,
|
|
1399
|
-
message: params.message
|
|
1400
|
-
}
|
|
1401
|
-
})).version?.number ?? params.nextVersion;
|
|
1783
|
+
function formatStepRows(steps) {
|
|
1784
|
+
const rows = steps.map((s) => ({
|
|
1785
|
+
name: s.name || "-",
|
|
1786
|
+
status: s.status || "-",
|
|
1787
|
+
dur: formatDuration(s.durationSeconds)
|
|
1788
|
+
}));
|
|
1789
|
+
const wn = Math.max(...rows.map((r) => r.name.length));
|
|
1790
|
+
const ws = Math.max(...rows.map((r) => r.status.length));
|
|
1791
|
+
return rows.map((r) => ` ${r.name.padEnd(wn)} ${r.status.padEnd(ws)} ${r.dur}`);
|
|
1402
1792
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1793
|
+
function formatPipelineDetail(detail, steps, nowMs) {
|
|
1794
|
+
const by = detail.creator ? ` by ${detail.creator}` : "";
|
|
1795
|
+
return [
|
|
1796
|
+
`Pipeline #${detail.buildNumber} ${detail.status || "-"}`,
|
|
1797
|
+
...detail.repo ? [`Repo: ${detail.repo}`] : [],
|
|
1798
|
+
`Ref: ${refWithCommit(detail)}`,
|
|
1799
|
+
...detail.trigger ? [`Trigger: ${detail.trigger}`] : [],
|
|
1800
|
+
`Duration: ${formatDuration(detail.durationSeconds)}`,
|
|
1801
|
+
`Created: ${relativeTime(detail.createdOn, nowMs)}${by}`,
|
|
1802
|
+
...steps.length > 0 ? [
|
|
1803
|
+
"",
|
|
1804
|
+
"Steps:",
|
|
1805
|
+
...formatStepRows(steps)
|
|
1806
|
+
] : []
|
|
1807
|
+
];
|
|
1410
1808
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
return
|
|
1414
|
-
author: await names.resolve(c.version?.authorId),
|
|
1415
|
-
created: c.version?.createdAt ?? "",
|
|
1416
|
-
body: parseAdfJson(c.body?.atlas_doc_format?.value)
|
|
1417
|
-
})));
|
|
1809
|
+
function refWithCommit(pipeline) {
|
|
1810
|
+
if (!pipeline.ref) return pipeline.commit || "-";
|
|
1811
|
+
return pipeline.commit ? `${pipeline.ref} (${pipeline.commit})` : pipeline.ref;
|
|
1418
1812
|
}
|
|
1419
|
-
function
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
} catch {
|
|
1424
|
-
return null;
|
|
1425
|
-
}
|
|
1813
|
+
function parseBuildNumber(arg) {
|
|
1814
|
+
const raw = (arg ?? "").replace(/^#/, "").trim();
|
|
1815
|
+
if (!/^\d+$/.test(raw)) throw new Error(`Invalid pipeline number "${arg ?? ""}". Expected a build number, e.g. 123.`);
|
|
1816
|
+
return Number.parseInt(raw, 10);
|
|
1426
1817
|
}
|
|
1427
|
-
var UserNames = class {
|
|
1428
|
-
client;
|
|
1429
|
-
cache = /* @__PURE__ */ new Map();
|
|
1430
|
-
constructor(client) {
|
|
1431
|
-
this.client = client;
|
|
1432
|
-
}
|
|
1433
|
-
async resolve(accountId) {
|
|
1434
|
-
if (!accountId) return "";
|
|
1435
|
-
const cached = this.cache.get(accountId);
|
|
1436
|
-
if (cached !== void 0) return cached;
|
|
1437
|
-
const name = await this.fetchDisplayName(accountId) ?? accountId;
|
|
1438
|
-
this.cache.set(accountId, name);
|
|
1439
|
-
return name;
|
|
1440
|
-
}
|
|
1441
|
-
async fetchDisplayName(accountId) {
|
|
1442
|
-
try {
|
|
1443
|
-
return (await this.client.getJson(`/wiki/rest/api/user?accountId=${encodeURIComponent(accountId)}`)).displayName || null;
|
|
1444
|
-
} catch {
|
|
1445
|
-
return null;
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
};
|
|
1449
1818
|
//#endregion
|
|
1450
|
-
//#region src/
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, body) => {
|
|
1462
|
-
if (body[0] === "#") {
|
|
1463
|
-
const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
|
|
1464
|
-
return isCodePoint(code) ? String.fromCodePoint(code) : match;
|
|
1465
|
-
}
|
|
1466
|
-
return NAMED[body.toLowerCase()] ?? match;
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
function isCodePoint(code) {
|
|
1470
|
-
return Number.isFinite(code) && code >= 0 && code <= MAX_CODE_POINT;
|
|
1819
|
+
//#region src/cli/bitbucket.ts
|
|
1820
|
+
function registerBitbucket(bitbucket) {
|
|
1821
|
+
bitbucket.description("Bitbucket commands");
|
|
1822
|
+
bitbucket.command("login").description("Store Bitbucket workspace and API token").action(bareAction(bitbucketLogin));
|
|
1823
|
+
bitbucket.command("logout").description("Remove stored Bitbucket credentials").action(bareAction(bitbucketLogout));
|
|
1824
|
+
bitbucket.command("status").description("Show the current Bitbucket login").action(bareAction(bitbucketStatus));
|
|
1825
|
+
bitbucket.command("pipelines").description("List recent pipeline runs for a repo").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").action(bitbucketAction(bitbucketPipelines));
|
|
1826
|
+
bitbucket.command("prs").description("List pull requests for a repo").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").option("-s, --state <state...>", "open, merged, declined, or superseded").option("--all", "include every state").option("-a, --author <who>", "limit to an author ('me', an account id, or a uuid)").option("--reviewer <who>", "limit to a reviewer ('me', an account id, or a uuid)").option("-q, --query <bbql>", "raw Bitbucket query (not for use with --author or --reviewer)").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").action(bitbucketAction(bitbucketPrs));
|
|
1827
|
+
bitbucket.command("pr <id>").description("Show one pull request, its reviewers, files, and comments").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").option("--all-comments", "show all comments instead of the last 5").option("--no-pager", "print directly instead of paging long output").option("--json", "output the pull request as JSON").action(bitbucketAction(bitbucketPr));
|
|
1828
|
+
bitbucket.command("pipeline <number>").description("Show one pipeline run and its steps").option("-r, --repo <repo>", "workspace/slug, or a bare slug (defaults to config)").action(bitbucketAction(bitbucketPipeline));
|
|
1829
|
+
return bitbucket;
|
|
1471
1830
|
}
|
|
1472
1831
|
//#endregion
|
|
1473
|
-
//#region src/
|
|
1474
|
-
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
cql,
|
|
1478
|
-
limit: String(params.limit),
|
|
1479
|
-
expand: "space"
|
|
1480
|
-
});
|
|
1481
|
-
const results = (await client.getJson(`/wiki/rest/api/search?${query.toString()}`)).results ?? [];
|
|
1482
|
-
const serverPageWasFull = results.length === params.limit;
|
|
1832
|
+
//#region src/adf/from-markdown.ts
|
|
1833
|
+
function mediaNode(source, alt) {
|
|
1834
|
+
const attrs = { ...source };
|
|
1835
|
+
if (alt) attrs["alt"] = alt;
|
|
1483
1836
|
return {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
})),
|
|
1491
|
-
hasMore: serverPageWasFull
|
|
1837
|
+
type: "mediaSingle",
|
|
1838
|
+
attrs: { layout: "center" },
|
|
1839
|
+
content: [{
|
|
1840
|
+
type: "media",
|
|
1841
|
+
attrs
|
|
1842
|
+
}]
|
|
1492
1843
|
};
|
|
1493
1844
|
}
|
|
1494
|
-
function
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
if (params.space) clauses.push(`space = ${cqlStringLiteral(params.space)}`);
|
|
1499
|
-
if (params.text) clauses.push(`text ~ ${cqlStringLiteral(params.text)}`);
|
|
1500
|
-
return `${clauses.join(" AND ")} ORDER BY lastmodified DESC`;
|
|
1501
|
-
}
|
|
1502
|
-
function cqlStringLiteral(value) {
|
|
1503
|
-
return `"${value.replace(/(["\\])/g, "\\$1")}"`;
|
|
1504
|
-
}
|
|
1505
|
-
//#endregion
|
|
1506
|
-
//#region src/commands/resolve-ref.ts
|
|
1507
|
-
async function resolveRef(ask, arg, kind) {
|
|
1508
|
-
const raw = arg ?? await ask.text({
|
|
1509
|
-
message: kind.message,
|
|
1510
|
-
flag: kind.flag,
|
|
1511
|
-
required: true
|
|
1845
|
+
function imageHrefs(md) {
|
|
1846
|
+
const hrefs = /* @__PURE__ */ new Set();
|
|
1847
|
+
marked.walkTokens(marked.lexer(md), (token) => {
|
|
1848
|
+
if (token.type === "image") hrefs.add(token.href);
|
|
1512
1849
|
});
|
|
1513
|
-
|
|
1514
|
-
if (!ref) throw new Error(kind.notFound(raw));
|
|
1515
|
-
return ref;
|
|
1850
|
+
return [...hrefs];
|
|
1516
1851
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
return `[embedded media: ${alt || attrs.id || "media"}]`;
|
|
1852
|
+
function markdownToAdf(md, options = {}) {
|
|
1853
|
+
const ctx = { resolveImage: options.resolveImage ?? externalMedia };
|
|
1854
|
+
return {
|
|
1855
|
+
type: "doc",
|
|
1856
|
+
version: 1,
|
|
1857
|
+
content: blocks(marked.lexer(md), ctx)
|
|
1858
|
+
};
|
|
1525
1859
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
1860
|
+
function blocks(tokens, ctx) {
|
|
1861
|
+
const out = [];
|
|
1862
|
+
for (const token of tokens) out.push(...block(token, ctx));
|
|
1863
|
+
return out;
|
|
1528
1864
|
}
|
|
1529
|
-
function
|
|
1530
|
-
switch (
|
|
1531
|
-
case "
|
|
1532
|
-
case "
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
case "
|
|
1541
|
-
case "
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
default: return node.text ?? "";
|
|
1865
|
+
function block(token, ctx) {
|
|
1866
|
+
switch (token.type) {
|
|
1867
|
+
case "space": return [];
|
|
1868
|
+
case "heading": return [{
|
|
1869
|
+
type: "heading",
|
|
1870
|
+
attrs: { level: clampLevel(token.depth) },
|
|
1871
|
+
content: inline(token.tokens ?? [], ctx)
|
|
1872
|
+
}];
|
|
1873
|
+
case "paragraph": return paragraphsSplitAtImages(token.tokens ?? [], ctx);
|
|
1874
|
+
case "text": return paragraphsSplitAtImages(token.tokens ?? [textToken(token.text)], ctx);
|
|
1875
|
+
case "list": return [list(token, ctx)];
|
|
1876
|
+
case "code": return [codeBlock(token)];
|
|
1877
|
+
case "blockquote": return [{
|
|
1878
|
+
type: "blockquote",
|
|
1879
|
+
content: blocks(token.tokens ?? [], ctx)
|
|
1880
|
+
}];
|
|
1881
|
+
case "table": return [table(token, ctx)];
|
|
1882
|
+
case "hr": return [{ type: "rule" }];
|
|
1883
|
+
default: return [];
|
|
1549
1884
|
}
|
|
1550
1885
|
}
|
|
1551
|
-
function
|
|
1552
|
-
|
|
1553
|
-
let
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
if (typeof value === "string") href = value;
|
|
1571
|
-
break;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
if (href) out = `[${out}](${href})`;
|
|
1886
|
+
function paragraphsSplitAtImages(tokens, ctx) {
|
|
1887
|
+
const out = [];
|
|
1888
|
+
let buffer = [];
|
|
1889
|
+
const flush = () => {
|
|
1890
|
+
if (buffer.length === 0) return;
|
|
1891
|
+
const nodes = inline(buffer, ctx);
|
|
1892
|
+
if (nodes.length > 0) out.push({
|
|
1893
|
+
type: "paragraph",
|
|
1894
|
+
content: nodes
|
|
1895
|
+
});
|
|
1896
|
+
buffer = [];
|
|
1897
|
+
};
|
|
1898
|
+
for (const token of tokens) if (token.type === "image") {
|
|
1899
|
+
flush();
|
|
1900
|
+
const image = token;
|
|
1901
|
+
const node = ctx.resolveImage(image.href, image.text ?? "");
|
|
1902
|
+
if (node) out.push(node);
|
|
1903
|
+
} else buffer.push(token);
|
|
1904
|
+
flush();
|
|
1575
1905
|
return out;
|
|
1576
1906
|
}
|
|
1577
|
-
function
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1907
|
+
function list(token, ctx) {
|
|
1908
|
+
if (token.items.length > 0 && token.items.every((i) => i.task)) return {
|
|
1909
|
+
type: "taskList",
|
|
1910
|
+
attrs: { localId: randomUUID() },
|
|
1911
|
+
content: token.items.map((item) => ({
|
|
1912
|
+
type: "taskItem",
|
|
1913
|
+
attrs: {
|
|
1914
|
+
localId: randomUUID(),
|
|
1915
|
+
state: item.checked ? "DONE" : "TODO"
|
|
1916
|
+
},
|
|
1917
|
+
content: inline(taskItemInline(item), ctx)
|
|
1918
|
+
}))
|
|
1919
|
+
};
|
|
1920
|
+
const node = {
|
|
1921
|
+
type: token.ordered ? "orderedList" : "bulletList",
|
|
1922
|
+
content: token.items.map((item) => listItem(item, ctx))
|
|
1923
|
+
};
|
|
1924
|
+
const start = Number(token.start);
|
|
1925
|
+
if (token.ordered && Number.isFinite(start) && start !== 1) node.attrs = { order: start };
|
|
1926
|
+
return node;
|
|
1927
|
+
}
|
|
1928
|
+
function listItem(item, ctx) {
|
|
1929
|
+
const children = blocks(item.tokens.filter((t) => t.type !== "checkbox"), ctx);
|
|
1930
|
+
return {
|
|
1931
|
+
type: "listItem",
|
|
1932
|
+
content: children.length > 0 ? children : [emptyParagraph()]
|
|
1933
|
+
};
|
|
1581
1934
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
return (doc.type === "doc" ? renderBlocks(doc.content ?? [], ctx, "") : renderBlock(doc, ctx, "")).trim();
|
|
1935
|
+
function emptyParagraph() {
|
|
1936
|
+
return {
|
|
1937
|
+
type: "paragraph",
|
|
1938
|
+
content: []
|
|
1939
|
+
};
|
|
1588
1940
|
}
|
|
1589
|
-
function
|
|
1590
|
-
|
|
1941
|
+
function taskItemInline(item) {
|
|
1942
|
+
const first = item.tokens.find((t) => t.type === "text");
|
|
1943
|
+
if (first && "tokens" in first && first.tokens) return first.tokens;
|
|
1944
|
+
return [textToken(item.text)];
|
|
1591
1945
|
}
|
|
1592
|
-
function
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
case "taskList": return renderTaskList(node, ctx, indent);
|
|
1602
|
-
case "decisionList": return renderDecisionList(node, ctx, indent);
|
|
1603
|
-
case "codeBlock": return renderCodeBlock(node, indent);
|
|
1604
|
-
case "blockquote": return prefixLines(renderBlocks(node.content ?? [], ctx, ""), `${indent}> `);
|
|
1605
|
-
case "panel": return renderPanel(node, ctx, indent);
|
|
1606
|
-
case "rule": return `${indent}---`;
|
|
1607
|
-
case "table": return renderTable(node, ctx);
|
|
1608
|
-
case "mediaSingle":
|
|
1609
|
-
case "mediaGroup": return renderBlocks(node.content ?? [], ctx, indent);
|
|
1610
|
-
case "media": return indent + renderMedia(node, ctx);
|
|
1611
|
-
case "expand":
|
|
1612
|
-
case "nestedExpand": return renderExpand(node, ctx, indent);
|
|
1613
|
-
default:
|
|
1614
|
-
if (node.content?.length) return renderBlocks(node.content, ctx, indent);
|
|
1615
|
-
return node.text ? indent + node.text : "";
|
|
1616
|
-
}
|
|
1946
|
+
function codeBlock(token) {
|
|
1947
|
+
return {
|
|
1948
|
+
type: "codeBlock",
|
|
1949
|
+
attrs: token.lang ? { language: token.lang } : {},
|
|
1950
|
+
content: token.text.length > 0 ? [{
|
|
1951
|
+
type: "text",
|
|
1952
|
+
text: token.text
|
|
1953
|
+
}] : []
|
|
1954
|
+
};
|
|
1617
1955
|
}
|
|
1618
|
-
function
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1956
|
+
function table(token, ctx) {
|
|
1957
|
+
return {
|
|
1958
|
+
type: "table",
|
|
1959
|
+
content: [{
|
|
1960
|
+
type: "tableRow",
|
|
1961
|
+
content: token.header.map((cell) => tableCell(cell, "tableHeader", ctx))
|
|
1962
|
+
}, ...token.rows.map((row) => ({
|
|
1963
|
+
type: "tableRow",
|
|
1964
|
+
content: row.map((cell) => tableCell(cell, "tableCell", ctx))
|
|
1965
|
+
}))]
|
|
1966
|
+
};
|
|
1623
1967
|
}
|
|
1624
|
-
function
|
|
1625
|
-
|
|
1626
|
-
|
|
1968
|
+
function tableCell(cell, type, ctx) {
|
|
1969
|
+
return {
|
|
1970
|
+
type,
|
|
1971
|
+
content: [{
|
|
1972
|
+
type: "paragraph",
|
|
1973
|
+
content: inline(cell.tokens, ctx)
|
|
1974
|
+
}]
|
|
1975
|
+
};
|
|
1627
1976
|
}
|
|
1628
|
-
function
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1977
|
+
function inline(tokens, ctx, marks = []) {
|
|
1978
|
+
const out = [];
|
|
1979
|
+
for (const token of tokens) out.push(...inlineNode(token, ctx, marks));
|
|
1980
|
+
return out;
|
|
1632
1981
|
}
|
|
1633
|
-
function
|
|
1634
|
-
|
|
1982
|
+
function inlineNode(token, ctx, marks) {
|
|
1983
|
+
switch (token.type) {
|
|
1984
|
+
case "text":
|
|
1985
|
+
case "escape": {
|
|
1986
|
+
const t = token;
|
|
1987
|
+
if ("tokens" in t && t.tokens?.length) return inline(t.tokens, ctx, marks);
|
|
1988
|
+
return textNode(t.text, marks);
|
|
1989
|
+
}
|
|
1990
|
+
case "strong": return inline(token.tokens, ctx, withUniqueMark(marks, { type: "strong" }));
|
|
1991
|
+
case "em": return inline(token.tokens, ctx, withUniqueMark(marks, { type: "em" }));
|
|
1992
|
+
case "del": return inline(token.tokens, ctx, withUniqueMark(marks, { type: "strike" }));
|
|
1993
|
+
case "codespan": return textNode(token.text, withUniqueMark(marks, { type: "code" }));
|
|
1994
|
+
case "link": {
|
|
1995
|
+
const link = token;
|
|
1996
|
+
return inline(link.tokens, ctx, withUniqueMark(marks, {
|
|
1997
|
+
type: "link",
|
|
1998
|
+
attrs: { href: link.href }
|
|
1999
|
+
}));
|
|
2000
|
+
}
|
|
2001
|
+
case "br": return [{ type: "hardBreak" }];
|
|
2002
|
+
case "html": return textNode(token.text, marks);
|
|
2003
|
+
default: return "text" in token && token.text ? textNode(token.text, marks) : [];
|
|
2004
|
+
}
|
|
1635
2005
|
}
|
|
1636
|
-
function
|
|
1637
|
-
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
2006
|
+
function textNode(text, marks) {
|
|
2007
|
+
if (text.length === 0) return [];
|
|
2008
|
+
const node = {
|
|
2009
|
+
type: "text",
|
|
2010
|
+
text
|
|
2011
|
+
};
|
|
2012
|
+
if (marks.length > 0) node.marks = marks;
|
|
2013
|
+
return [node];
|
|
1641
2014
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
note: "Note",
|
|
1645
|
-
warning: "Warning",
|
|
1646
|
-
success: "Success",
|
|
1647
|
-
error: "Error"
|
|
1648
|
-
};
|
|
1649
|
-
function renderPanel(node, ctx, indent) {
|
|
1650
|
-
const type = typeof node.attrs?.["panelType"] === "string" ? node.attrs["panelType"] : "info";
|
|
1651
|
-
return prefixLines(`**${PANEL_LABELS[type] ?? "Note"}**\n\n${renderBlocks(node.content ?? [], ctx, "")}`, `${indent}> `);
|
|
2015
|
+
function withUniqueMark(marks, mark) {
|
|
2016
|
+
return [...marks.filter((m) => m.type !== mark.type), mark];
|
|
1652
2017
|
}
|
|
1653
|
-
function
|
|
1654
|
-
return
|
|
2018
|
+
function externalMedia(href, alt) {
|
|
2019
|
+
return mediaNode({
|
|
2020
|
+
type: "external",
|
|
2021
|
+
url: href
|
|
2022
|
+
}, alt);
|
|
1655
2023
|
}
|
|
1656
|
-
function
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
const copy = [...r];
|
|
1663
|
-
while (copy.length < cols) copy.push("");
|
|
1664
|
-
return copy;
|
|
2024
|
+
function textToken(text) {
|
|
2025
|
+
return {
|
|
2026
|
+
type: "text",
|
|
2027
|
+
raw: text,
|
|
2028
|
+
text,
|
|
2029
|
+
escaped: false
|
|
1665
2030
|
};
|
|
1666
|
-
const header = pad(grid[0] ?? []);
|
|
1667
|
-
const lines = [`| ${header.join(" | ")} |`, `| ${header.map(() => "---").join(" | ")} |`];
|
|
1668
|
-
for (const row of grid.slice(1)) lines.push(`| ${pad(row).join(" | ")} |`);
|
|
1669
|
-
return lines.join("\n");
|
|
1670
|
-
}
|
|
1671
|
-
function renderCellOnOneLine(cell, ctx) {
|
|
1672
|
-
return renderBlocks(cell.content ?? [], ctx, "").replace(/\n+/g, " ").replace(/\|/g, "\\|").trim();
|
|
1673
|
-
}
|
|
1674
|
-
function prefixLines(text, prefix) {
|
|
1675
|
-
return text.split("\n").map((line) => line.length > 0 ? prefix + line : prefix.trimEnd()).join("\n");
|
|
1676
2031
|
}
|
|
1677
2032
|
function clampLevel(value) {
|
|
1678
|
-
|
|
1679
|
-
return Math.min(6, Math.max(1, n));
|
|
1680
|
-
}
|
|
1681
|
-
function toNumber(value) {
|
|
1682
|
-
return typeof value === "number" ? value : void 0;
|
|
2033
|
+
return Math.min(6, Math.max(1, Math.trunc(value) || 1));
|
|
1683
2034
|
}
|
|
1684
2035
|
//#endregion
|
|
1685
|
-
//#region src/
|
|
1686
|
-
const
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
const DETAILS_OPEN = /^(\s*)<details><summary>(.*)<\/summary>$/;
|
|
1694
|
-
const DETAILS_CLOSE = /^\s*<\/details>$/;
|
|
1695
|
-
const FENCE_OPEN = /^((?:\s|> |- |\d+\. )*)```(\S*)$/;
|
|
1696
|
-
const FENCE = "```";
|
|
1697
|
-
const INLINE = /(`[^`]+`)|\[([^\]]+)\]\(((?:[^()]|\([^()]*\))+)\)|\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|\*(?!\*)(.+?)(?<!\*)\*(?!\*)|~~(.+?)~~/g;
|
|
1698
|
-
const TOKEN_STYLES = {
|
|
1699
|
-
keyword: kleur.magenta,
|
|
1700
|
-
built_in: kleur.magenta,
|
|
1701
|
-
literal: kleur.magenta,
|
|
1702
|
-
string: kleur.green,
|
|
1703
|
-
regexp: kleur.green,
|
|
1704
|
-
addition: kleur.green,
|
|
1705
|
-
number: kleur.yellow,
|
|
1706
|
-
symbol: kleur.yellow,
|
|
1707
|
-
meta: kleur.yellow,
|
|
1708
|
-
attr: kleur.yellow,
|
|
1709
|
-
comment: kleur.dim,
|
|
1710
|
-
quote: kleur.dim,
|
|
1711
|
-
deletion: kleur.dim,
|
|
1712
|
-
title: kleur.cyan,
|
|
1713
|
-
type: kleur.cyan,
|
|
1714
|
-
section: kleur.cyan
|
|
1715
|
-
};
|
|
1716
|
-
const lowlight = createLowlight(common);
|
|
1717
|
-
function highlightMarkdown(md) {
|
|
1718
|
-
const lines = md.split("\n");
|
|
1719
|
-
const out = [];
|
|
1720
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1721
|
-
const line = lines[i] ?? "";
|
|
1722
|
-
const fence = line.match(FENCE_OPEN);
|
|
1723
|
-
if (!fence) {
|
|
1724
|
-
out.push(highlightLine(line, lines[i + 1]));
|
|
1725
|
-
continue;
|
|
1726
|
-
}
|
|
1727
|
-
const [, prefix = "", lang = ""] = fence;
|
|
1728
|
-
const styledPrefix = prefix.replace(/\S+/g, (marker) => kleur.dim(marker));
|
|
1729
|
-
out.push(`${styledPrefix}${kleur.dim(`${FENCE}${lang}`)}`);
|
|
1730
|
-
const code = [];
|
|
1731
|
-
const prefixed = [];
|
|
1732
|
-
let j = i + 1;
|
|
1733
|
-
for (; j < lines.length; j++) {
|
|
1734
|
-
const raw = lines[j] ?? "";
|
|
1735
|
-
const hasPrefix = raw.startsWith(prefix) || raw === prefix.trimEnd();
|
|
1736
|
-
const body = raw.startsWith(prefix) ? raw.slice(prefix.length) : hasPrefix ? "" : raw;
|
|
1737
|
-
if (body === FENCE) break;
|
|
1738
|
-
code.push(body);
|
|
1739
|
-
prefixed.push(hasPrefix);
|
|
1740
|
-
}
|
|
1741
|
-
if (code.length > 0) highlightCode(code.join("\n"), lang).split("\n").forEach((codeLine, k) => {
|
|
1742
|
-
const lead = prefixed[k] ? styledPrefix : "";
|
|
1743
|
-
out.push(codeLine ? `${lead}${codeLine}` : lead.trimEnd());
|
|
1744
|
-
});
|
|
1745
|
-
if (j < lines.length) {
|
|
1746
|
-
const lead = (lines[j] ?? "").startsWith(prefix) ? styledPrefix : "";
|
|
1747
|
-
out.push(`${lead}${kleur.dim(FENCE)}`);
|
|
1748
|
-
}
|
|
1749
|
-
i = j;
|
|
1750
|
-
}
|
|
1751
|
-
return out.join("\n");
|
|
2036
|
+
//#region src/api/confluence-attachments.ts
|
|
2037
|
+
const UNKNOWN_SIZE = -1;
|
|
2038
|
+
async function listAttachments(client, id) {
|
|
2039
|
+
return (await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}/attachments?limit=250`)).results.map((a) => ({
|
|
2040
|
+
filename: a.title ?? a.id,
|
|
2041
|
+
fileId: mediaNodeId(a),
|
|
2042
|
+
size: typeof a.fileSize === "number" ? a.fileSize : UNKNOWN_SIZE
|
|
2043
|
+
}));
|
|
1752
2044
|
}
|
|
1753
|
-
function
|
|
1754
|
-
|
|
1755
|
-
return renderTree(lowlight.highlight(lang, code), (text) => text);
|
|
2045
|
+
async function uploadAttachment(client, pageId, filename, bytes) {
|
|
2046
|
+
return (await client.postMultipart(`/wiki/rest/api/content/${encodeURIComponent(pageId)}/child/attachment`, filename, bytes)).results?.[0]?.extensions?.fileId ?? await fileIdByListing(client, pageId, filename);
|
|
1756
2047
|
}
|
|
1757
|
-
function
|
|
1758
|
-
|
|
1759
|
-
if (
|
|
1760
|
-
|
|
1761
|
-
const next = own ? (text) => style(own(text)) : style;
|
|
1762
|
-
return node.children.map((child) => renderTree(child, next)).join("");
|
|
2048
|
+
async function fileIdByListing(client, pageId, filename) {
|
|
2049
|
+
const match = (await listAttachments(client, pageId)).find((a) => a.filename === filename);
|
|
2050
|
+
if (match) return match.fileId;
|
|
2051
|
+
throw new Error(`Upload of "${filename}" did not return a fileId.`);
|
|
1763
2052
|
}
|
|
1764
|
-
function
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
}
|
|
2053
|
+
async function fetchAttachments(client, id) {
|
|
2054
|
+
return (await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}/attachments?limit=250`)).results.filter((a) => a.downloadLink).map((a) => ({
|
|
2055
|
+
mediaId: mediaNodeId(a),
|
|
2056
|
+
filename: a.title ?? a.id,
|
|
2057
|
+
url: withWikiContextPath(a.downloadLink ?? "")
|
|
2058
|
+
}));
|
|
1771
2059
|
}
|
|
1772
|
-
function
|
|
1773
|
-
return
|
|
2060
|
+
function mediaNodeId(a) {
|
|
2061
|
+
return a.fileId ?? a.id;
|
|
1774
2062
|
}
|
|
1775
|
-
function
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const [whole, indent = ""] = quote;
|
|
1779
|
-
const rest = line.slice(whole.length);
|
|
1780
|
-
const gap = whole.endsWith(" ") ? " " : "";
|
|
1781
|
-
return `${indent}${kleur.dim(">")}${gap}${highlightLine(rest, stripPrefix(nextLine, whole))}`;
|
|
1782
|
-
}
|
|
1783
|
-
const list = line.match(LIST);
|
|
1784
|
-
if (list) {
|
|
1785
|
-
const [whole, indent = "", marker = "", checkbox] = list;
|
|
1786
|
-
const rest = line.slice(whole.length);
|
|
1787
|
-
const box = checkbox ? `${kleur.bold(checkbox.trim())} ` : "";
|
|
1788
|
-
return `${indent}${kleur.dim(marker)} ${box}${highlightLine(rest, void 0)}`;
|
|
1789
|
-
}
|
|
1790
|
-
if (HEADING.test(line)) return kleur.bold().cyan(line);
|
|
1791
|
-
if (RULE.test(line)) return kleur.dim(line);
|
|
1792
|
-
if (TABLE_SEPARATOR.test(line)) return kleur.dim(line);
|
|
1793
|
-
if (TABLE_ROW.test(line)) return tableRow(line, nextLine !== void 0 && TABLE_SEPARATOR.test(nextLine));
|
|
1794
|
-
const details = line.match(DETAILS_OPEN);
|
|
1795
|
-
if (details) {
|
|
1796
|
-
const [, indent = "", title = ""] = details;
|
|
1797
|
-
return `${indent}${kleur.dim("<details><summary>")}${highlightInline(title)}${kleur.dim("</summary>")}`;
|
|
1798
|
-
}
|
|
1799
|
-
if (DETAILS_CLOSE.test(line)) return kleur.dim(line);
|
|
1800
|
-
return highlightInline(line);
|
|
2063
|
+
function withWikiContextPath(link) {
|
|
2064
|
+
if (link.startsWith("http") || link.startsWith("/wiki")) return link;
|
|
2065
|
+
return `/wiki${link}`;
|
|
1801
2066
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
2067
|
+
//#endregion
|
|
2068
|
+
//#region src/api/confluence-pages.ts
|
|
2069
|
+
async function fetchPage(client, site, id) {
|
|
2070
|
+
const page = await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}?body-format=atlas_doc_format`);
|
|
2071
|
+
const names = new UserNames(client);
|
|
2072
|
+
const [spaceKey, attachments, comments, author] = await Promise.all([
|
|
2073
|
+
fetchSpaceKey(client, page.spaceId),
|
|
2074
|
+
fetchAttachments(client, id),
|
|
2075
|
+
fetchComments$1(client, id, names),
|
|
2076
|
+
names.resolve(page.version?.authorId ?? page.authorId)
|
|
2077
|
+
]);
|
|
2078
|
+
const webui = page._links?.webui ?? "";
|
|
2079
|
+
return {
|
|
2080
|
+
id: page.id,
|
|
2081
|
+
title: page.title,
|
|
2082
|
+
spaceKey,
|
|
2083
|
+
version: page.version?.number ?? 0,
|
|
2084
|
+
author,
|
|
2085
|
+
createdAt: page.createdAt ?? "",
|
|
2086
|
+
updatedAt: page.version?.createdAt ?? "",
|
|
2087
|
+
url: webui ? `${site}/wiki${webui}` : "",
|
|
2088
|
+
body: parseAdfJson(page.body?.atlas_doc_format?.value),
|
|
2089
|
+
attachments,
|
|
2090
|
+
comments
|
|
2091
|
+
};
|
|
1804
2092
|
}
|
|
1805
|
-
function
|
|
1806
|
-
const
|
|
1807
|
-
|
|
1808
|
-
|
|
2093
|
+
async function fetchPageState(client, id) {
|
|
2094
|
+
const page = await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}?body-format=atlas_doc_format`);
|
|
2095
|
+
return {
|
|
2096
|
+
version: page.version?.number ?? 0,
|
|
2097
|
+
title: page.title,
|
|
2098
|
+
body: parseAdfJson(page.body?.atlas_doc_format?.value)
|
|
2099
|
+
};
|
|
1809
2100
|
}
|
|
1810
|
-
function
|
|
1811
|
-
return
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2101
|
+
async function updatePage(client, id, params) {
|
|
2102
|
+
return (await client.putJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}`, {
|
|
2103
|
+
id,
|
|
2104
|
+
status: "current",
|
|
2105
|
+
title: params.title,
|
|
2106
|
+
body: {
|
|
2107
|
+
representation: "atlas_doc_format",
|
|
2108
|
+
value: JSON.stringify(params.body)
|
|
2109
|
+
},
|
|
2110
|
+
version: {
|
|
2111
|
+
number: params.nextVersion,
|
|
2112
|
+
message: params.message
|
|
2113
|
+
}
|
|
2114
|
+
})).version?.number ?? params.nextVersion;
|
|
1820
2115
|
}
|
|
1821
|
-
function
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2116
|
+
async function fetchSpaceKey(client, spaceId) {
|
|
2117
|
+
if (!spaceId) return "";
|
|
2118
|
+
try {
|
|
2119
|
+
return (await client.getJson(`/wiki/api/v2/spaces/${encodeURIComponent(spaceId)}`)).key ?? "";
|
|
2120
|
+
} catch {
|
|
2121
|
+
return "";
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
async function fetchComments$1(client, id, names) {
|
|
2125
|
+
const res = await client.getJson(`/wiki/api/v2/pages/${encodeURIComponent(id)}/footer-comments?body-format=atlas_doc_format&limit=250`);
|
|
2126
|
+
return Promise.all(res.results.map(async (c) => ({
|
|
2127
|
+
author: await names.resolve(c.version?.authorId),
|
|
2128
|
+
created: c.version?.createdAt ?? "",
|
|
2129
|
+
body: parseAdfJson(c.body?.atlas_doc_format?.value)
|
|
2130
|
+
})));
|
|
2131
|
+
}
|
|
2132
|
+
function parseAdfJson(value) {
|
|
2133
|
+
if (!value) return null;
|
|
2134
|
+
try {
|
|
2135
|
+
return JSON.parse(value);
|
|
2136
|
+
} catch {
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
1825
2139
|
}
|
|
2140
|
+
var UserNames = class {
|
|
2141
|
+
client;
|
|
2142
|
+
cache = /* @__PURE__ */ new Map();
|
|
2143
|
+
constructor(client) {
|
|
2144
|
+
this.client = client;
|
|
2145
|
+
}
|
|
2146
|
+
async resolve(accountId) {
|
|
2147
|
+
if (!accountId) return "";
|
|
2148
|
+
const cached = this.cache.get(accountId);
|
|
2149
|
+
if (cached !== void 0) return cached;
|
|
2150
|
+
const name = await this.fetchDisplayName(accountId) ?? accountId;
|
|
2151
|
+
this.cache.set(accountId, name);
|
|
2152
|
+
return name;
|
|
2153
|
+
}
|
|
2154
|
+
async fetchDisplayName(accountId) {
|
|
2155
|
+
try {
|
|
2156
|
+
return (await this.client.getJson(`/wiki/rest/api/user?accountId=${encodeURIComponent(accountId)}`)).displayName || null;
|
|
2157
|
+
} catch {
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
1826
2162
|
//#endregion
|
|
1827
|
-
//#region src/
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2163
|
+
//#region src/util/html.ts
|
|
2164
|
+
const MAX_CODE_POINT = 1114111;
|
|
2165
|
+
const NAMED = {
|
|
2166
|
+
amp: "&",
|
|
2167
|
+
lt: "<",
|
|
2168
|
+
gt: ">",
|
|
2169
|
+
quot: "\"",
|
|
2170
|
+
apos: "'",
|
|
2171
|
+
nbsp: " "
|
|
2172
|
+
};
|
|
2173
|
+
function decodeEntities(text) {
|
|
2174
|
+
return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (match, body) => {
|
|
2175
|
+
if (body[0] === "#") {
|
|
2176
|
+
const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
|
|
2177
|
+
return isCodePoint(code) ? String.fromCodePoint(code) : match;
|
|
2178
|
+
}
|
|
2179
|
+
return NAMED[body.toLowerCase()] ?? match;
|
|
2180
|
+
});
|
|
1832
2181
|
}
|
|
1833
|
-
function
|
|
1834
|
-
|
|
1835
|
-
return `${formatDateTime(iso).slice(0, 10)} (${relativeTime(iso, nowMs)})`;
|
|
2182
|
+
function isCodePoint(code) {
|
|
2183
|
+
return Number.isFinite(code) && code >= 0 && code <= MAX_CODE_POINT;
|
|
1836
2184
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2185
|
+
//#endregion
|
|
2186
|
+
//#region src/api/confluence-search.ts
|
|
2187
|
+
async function searchPages(client, site, params) {
|
|
2188
|
+
const cql = buildCql(params);
|
|
2189
|
+
const query = new URLSearchParams({
|
|
2190
|
+
cql,
|
|
2191
|
+
limit: String(params.limit),
|
|
2192
|
+
expand: "space"
|
|
2193
|
+
});
|
|
2194
|
+
const results = (await client.getJson(`/wiki/rest/api/search?${query.toString()}`)).results ?? [];
|
|
2195
|
+
const serverPageWasFull = results.length === params.limit;
|
|
2196
|
+
return {
|
|
2197
|
+
pages: results.filter((r) => r.content?.id).map((r) => ({
|
|
2198
|
+
id: r.content?.id ?? "",
|
|
2199
|
+
space: r.space?.key ?? r.resultGlobalContainer?.title ?? "",
|
|
2200
|
+
title: decodeEntities(r.content?.title ?? r.title ?? ""),
|
|
2201
|
+
updated: r.lastModified ?? "",
|
|
2202
|
+
url: r.url ? `${site}/wiki${r.url}` : ""
|
|
2203
|
+
})),
|
|
2204
|
+
hasMore: serverPageWasFull
|
|
2205
|
+
};
|
|
1840
2206
|
}
|
|
1841
|
-
function
|
|
1842
|
-
if (
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
lines.push("", commentHeader(comment), ...body ? [highlightMarkdown(body)] : []);
|
|
1849
|
-
}
|
|
1850
|
-
return lines;
|
|
2207
|
+
function buildCql(params) {
|
|
2208
|
+
if (params.cql) return params.cql;
|
|
2209
|
+
const clauses = ["type = page"];
|
|
2210
|
+
if (params.starred) clauses.push("favourite = currentUser()");
|
|
2211
|
+
if (params.space) clauses.push(`space = ${cqlStringLiteral(params.space)}`);
|
|
2212
|
+
if (params.text) clauses.push(`text ~ ${cqlStringLiteral(params.text)}`);
|
|
2213
|
+
return `${clauses.join(" AND ")} ORDER BY lastmodified DESC`;
|
|
1851
2214
|
}
|
|
1852
|
-
function
|
|
1853
|
-
return
|
|
2215
|
+
function cqlStringLiteral(value) {
|
|
2216
|
+
return `"${value.replace(/(["\\])/g, "\\$1")}"`;
|
|
1854
2217
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2218
|
+
//#endregion
|
|
2219
|
+
//#region src/commands/resolve-ref.ts
|
|
2220
|
+
async function resolveRef(ask, arg, kind) {
|
|
2221
|
+
const raw = arg ?? await ask.text({
|
|
2222
|
+
message: kind.message,
|
|
2223
|
+
flag: kind.flag,
|
|
2224
|
+
required: true
|
|
2225
|
+
});
|
|
2226
|
+
const ref = kind.parse(raw);
|
|
2227
|
+
if (!ref) throw new Error(kind.notFound(raw));
|
|
2228
|
+
return ref;
|
|
1862
2229
|
}
|
|
1863
2230
|
//#endregion
|
|
1864
2231
|
//#region src/api/attachments.ts
|
|
@@ -2376,7 +2743,7 @@ function formatPageView(page, nowMs, allComments) {
|
|
|
2376
2743
|
["URL", page.url]
|
|
2377
2744
|
]),
|
|
2378
2745
|
...bodyLines(page.body),
|
|
2379
|
-
...commentSection(page.comments, allComments),
|
|
2746
|
+
...commentSection(renderedComments(page.comments), { allComments }),
|
|
2380
2747
|
...attachmentSection(page.attachments)
|
|
2381
2748
|
];
|
|
2382
2749
|
}
|
|
@@ -3496,7 +3863,7 @@ function formatIssueView(issue, nowMs, allComments) {
|
|
|
3496
3863
|
["URL", issue.url]
|
|
3497
3864
|
]),
|
|
3498
3865
|
...bodyLines(issue.description),
|
|
3499
|
-
...commentSection(issue.comments, allComments),
|
|
3866
|
+
...commentSection(renderedComments(issue.comments), { allComments }),
|
|
3500
3867
|
...attachmentSection(issue.attachments)
|
|
3501
3868
|
];
|
|
3502
3869
|
}
|
|
@@ -3602,7 +3969,7 @@ function registerJira(jira) {
|
|
|
3602
3969
|
}
|
|
3603
3970
|
//#endregion
|
|
3604
3971
|
//#region package.json
|
|
3605
|
-
var version = "1.
|
|
3972
|
+
var version = "1.14.0";
|
|
3606
3973
|
//#endregion
|
|
3607
3974
|
//#region src/cli/build.ts
|
|
3608
3975
|
const PRODUCTS = {
|