atlass 1.9.0 → 1.10.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 +26 -12
- package/dist/bitbucket.mjs +1 -1
- package/dist/{build-DhED0Zw_.mjs → build-Colq1d4N.mjs} +340 -240
- package/dist/cli.mjs +1 -1
- package/dist/confluence.mjs +1 -1
- package/dist/jira.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,12 +44,19 @@ atlass bitbucket logout
|
|
|
44
44
|
```bash
|
|
45
45
|
atlass jira view PROJ-123
|
|
46
46
|
atlass jira view https://acme.atlassian.net/browse/PROJ-123
|
|
47
|
+
atlass confluence view 123456
|
|
48
|
+
atlass confluence view https://acme.atlassian.net/wiki/spaces/DEV/pages/123456/Title
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
Prints the issue to the terminal: fields,
|
|
50
|
-
(`--all-comments` for all of them), and attachment names. The
|
|
51
|
-
by category and the Markdown is syntax highlighted, code
|
|
52
|
-
turns off automatically when piping, or with `NO_COLOR`,
|
|
51
|
+
Prints the issue or page to the terminal: fields, the body, the last five
|
|
52
|
+
comments (`--all-comments` for all of them), and attachment names. The Jira
|
|
53
|
+
status is colored by category and the Markdown is syntax highlighted, code
|
|
54
|
+
blocks included; color turns off automatically when piping, or with `NO_COLOR`,
|
|
55
|
+
leaving plain Markdown.
|
|
56
|
+
|
|
57
|
+
Output taller than the terminal goes through a pager, `$PAGER` or `less`, with
|
|
58
|
+
`LESS=FRX` unless you have set `LESS` yourself. Pass `--no-pager` to print
|
|
59
|
+
directly. Piped output is never paged.
|
|
53
60
|
|
|
54
61
|
## Copy
|
|
55
62
|
|
|
@@ -96,12 +103,18 @@ body sent is everything between the H1 and `## Comments`.
|
|
|
96
103
|
atlass jira list
|
|
97
104
|
atlass jira list --project PROJ
|
|
98
105
|
atlass jira list --all
|
|
106
|
+
atlass confluence list
|
|
107
|
+
atlass confluence list --space DOCS
|
|
99
108
|
```
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
first, then To Do, and the most recently updated at the top of each
|
|
103
|
-
issues are left out; `--all` adds those updated in the last 30 days.
|
|
104
|
-
|
|
110
|
+
`jira list` shows issues assigned to you as `KEY Status Age Summary`, with
|
|
111
|
+
In Progress first, then To Do, and the most recently updated at the top of each
|
|
112
|
+
group. Done issues are left out; `--all` adds those updated in the last 30 days.
|
|
113
|
+
|
|
114
|
+
`confluence list` shows the pages you starred as `ID SPACE Age Title`, most
|
|
115
|
+
recently updated first. `--limit` works as for search.
|
|
116
|
+
|
|
117
|
+
For both, `--json` and `--copy` work as for search.
|
|
105
118
|
|
|
106
119
|
## Search
|
|
107
120
|
|
|
@@ -113,10 +126,11 @@ atlass confluence search "onboarding" --space DOCS
|
|
|
113
126
|
atlass confluence search --cql "label = runbook ORDER BY created DESC"
|
|
114
127
|
```
|
|
115
128
|
|
|
116
|
-
Filters combine with AND and sort by most recently updated.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
Filters combine with AND and sort by most recently updated. Rows use the same
|
|
130
|
+
columns as `jira list` and `confluence list`. `--jql` and `--cql` replace the
|
|
131
|
+
friendly filters. `--limit` defaults to 25, max 100. `--json` prints machine
|
|
132
|
+
output. Add `--copy` to pick results interactively and copy each one, with
|
|
133
|
+
`--out` as the target directory.
|
|
120
134
|
|
|
121
135
|
Discovery aids for the filters:
|
|
122
136
|
|
package/dist/bitbucket.mjs
CHANGED
|
@@ -4,11 +4,12 @@ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { Entry } from "@napi-rs/keyring";
|
|
7
|
+
import kleur from "kleur";
|
|
7
8
|
import { marked } from "marked";
|
|
8
9
|
import { randomUUID } from "node:crypto";
|
|
9
10
|
import { stripVTControlCharacters } from "node:util";
|
|
10
|
-
import kleur from "kleur";
|
|
11
11
|
import { common, createLowlight } from "lowlight";
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
12
13
|
//#region src/cli/run.ts
|
|
13
14
|
const SIGINT_EXIT_CODE = 130;
|
|
14
15
|
function run(fn) {
|
|
@@ -925,6 +926,7 @@ async function searchPages(client, site, params) {
|
|
|
925
926
|
id: r.content?.id ?? "",
|
|
926
927
|
space: r.space?.key ?? r.resultGlobalContainer?.title ?? "",
|
|
927
928
|
title: decodeEntities(r.content?.title ?? r.title ?? ""),
|
|
929
|
+
updated: r.lastModified ?? "",
|
|
928
930
|
url: r.url ? `${site}/wiki${r.url}` : ""
|
|
929
931
|
})),
|
|
930
932
|
hasMore: serverPageWasFull
|
|
@@ -933,6 +935,7 @@ async function searchPages(client, site, params) {
|
|
|
933
935
|
function buildCql(params) {
|
|
934
936
|
if (params.cql) return params.cql;
|
|
935
937
|
const clauses = ["type = page"];
|
|
938
|
+
if (params.starred) clauses.push("favourite = currentUser()");
|
|
936
939
|
if (params.space) clauses.push(`space = ${cqlStringLiteral(params.space)}`);
|
|
937
940
|
if (params.text) clauses.push(`text ~ ${cqlStringLiteral(params.text)}`);
|
|
938
941
|
return `${clauses.join(" AND ")} ORDER BY lastmodified DESC`;
|
|
@@ -1013,6 +1016,23 @@ async function resolveRef(arg, kind) {
|
|
|
1013
1016
|
}
|
|
1014
1017
|
//#endregion
|
|
1015
1018
|
//#region src/commands/search-run.ts
|
|
1019
|
+
function alignedRows(items, nowMs, cells) {
|
|
1020
|
+
const rows = items.map((item) => ({
|
|
1021
|
+
...cells(item),
|
|
1022
|
+
age: relativeTime(item.updated, nowMs),
|
|
1023
|
+
json: { ...item }
|
|
1024
|
+
}));
|
|
1025
|
+
const width = (pick) => Math.max(...rows.map((row) => pick(row).length));
|
|
1026
|
+
const idWidth = width((row) => row.id);
|
|
1027
|
+
const labelWidth = width((row) => row.label);
|
|
1028
|
+
const ageWidth = width((row) => row.age);
|
|
1029
|
+
return rows.map((row) => ({
|
|
1030
|
+
id: row.id,
|
|
1031
|
+
fixedColumns: `${row.id.padEnd(idWidth)} ${row.color(row.label.padEnd(labelWidth))} ${row.age.padEnd(ageWidth)}`,
|
|
1032
|
+
freeText: row.text,
|
|
1033
|
+
json: row.json
|
|
1034
|
+
}));
|
|
1035
|
+
}
|
|
1016
1036
|
const COPY_CONCURRENCY = 5;
|
|
1017
1037
|
async function runSearch(rows, options, noun, copyOne) {
|
|
1018
1038
|
if (options.json) {
|
|
@@ -1240,6 +1260,185 @@ function formatDate(timestamp) {
|
|
|
1240
1260
|
return new Date(ms).toISOString().slice(0, 10);
|
|
1241
1261
|
}
|
|
1242
1262
|
//#endregion
|
|
1263
|
+
//#region src/markdown/highlight.ts
|
|
1264
|
+
const HEADING = /^#{1,6} /;
|
|
1265
|
+
const QUOTE = /^(\s*)>( |$)/;
|
|
1266
|
+
const LIST = /^(\s*)(-|\d+\.) (\[[ x]\] )?/;
|
|
1267
|
+
const RULE = /^\s*---$/;
|
|
1268
|
+
const TABLE_ROW = /^\|.*\|$/;
|
|
1269
|
+
const TABLE_SEPARATOR = /^\|( --- \|)+$/;
|
|
1270
|
+
const CELL_DIVIDER = /(?<!\\)\|/;
|
|
1271
|
+
const DETAILS_OPEN = /^(\s*)<details><summary>(.*)<\/summary>$/;
|
|
1272
|
+
const DETAILS_CLOSE = /^\s*<\/details>$/;
|
|
1273
|
+
const FENCE_OPEN = /^((?:\s|> |- |\d+\. )*)```(\S*)$/;
|
|
1274
|
+
const FENCE = "```";
|
|
1275
|
+
const INLINE = /(`[^`]+`)|\[([^\]]+)\]\(((?:[^()]|\([^()]*\))+)\)|\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|\*(?!\*)(.+?)(?<!\*)\*(?!\*)|~~(.+?)~~/g;
|
|
1276
|
+
const TOKEN_STYLES = {
|
|
1277
|
+
keyword: kleur.magenta,
|
|
1278
|
+
built_in: kleur.magenta,
|
|
1279
|
+
literal: kleur.magenta,
|
|
1280
|
+
string: kleur.green,
|
|
1281
|
+
regexp: kleur.green,
|
|
1282
|
+
addition: kleur.green,
|
|
1283
|
+
number: kleur.yellow,
|
|
1284
|
+
symbol: kleur.yellow,
|
|
1285
|
+
meta: kleur.yellow,
|
|
1286
|
+
attr: kleur.yellow,
|
|
1287
|
+
comment: kleur.dim,
|
|
1288
|
+
quote: kleur.dim,
|
|
1289
|
+
deletion: kleur.dim,
|
|
1290
|
+
title: kleur.cyan,
|
|
1291
|
+
type: kleur.cyan,
|
|
1292
|
+
section: kleur.cyan
|
|
1293
|
+
};
|
|
1294
|
+
const lowlight = createLowlight(common);
|
|
1295
|
+
function highlightMarkdown(md) {
|
|
1296
|
+
const lines = md.split("\n");
|
|
1297
|
+
const out = [];
|
|
1298
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1299
|
+
const line = lines[i] ?? "";
|
|
1300
|
+
const fence = line.match(FENCE_OPEN);
|
|
1301
|
+
if (!fence) {
|
|
1302
|
+
out.push(highlightLine(line, lines[i + 1]));
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const [, prefix = "", lang = ""] = fence;
|
|
1306
|
+
const styledPrefix = prefix.replace(/\S+/g, (marker) => kleur.dim(marker));
|
|
1307
|
+
out.push(`${styledPrefix}${kleur.dim(`${FENCE}${lang}`)}`);
|
|
1308
|
+
const code = [];
|
|
1309
|
+
const prefixed = [];
|
|
1310
|
+
let j = i + 1;
|
|
1311
|
+
for (; j < lines.length; j++) {
|
|
1312
|
+
const raw = lines[j] ?? "";
|
|
1313
|
+
const hasPrefix = raw.startsWith(prefix) || raw === prefix.trimEnd();
|
|
1314
|
+
const body = raw.startsWith(prefix) ? raw.slice(prefix.length) : hasPrefix ? "" : raw;
|
|
1315
|
+
if (body === FENCE) break;
|
|
1316
|
+
code.push(body);
|
|
1317
|
+
prefixed.push(hasPrefix);
|
|
1318
|
+
}
|
|
1319
|
+
if (code.length > 0) highlightCode(code.join("\n"), lang).split("\n").forEach((codeLine, k) => {
|
|
1320
|
+
const lead = prefixed[k] ? styledPrefix : "";
|
|
1321
|
+
out.push(codeLine ? `${lead}${codeLine}` : lead.trimEnd());
|
|
1322
|
+
});
|
|
1323
|
+
if (j < lines.length) {
|
|
1324
|
+
const lead = (lines[j] ?? "").startsWith(prefix) ? styledPrefix : "";
|
|
1325
|
+
out.push(`${lead}${kleur.dim(FENCE)}`);
|
|
1326
|
+
}
|
|
1327
|
+
i = j;
|
|
1328
|
+
}
|
|
1329
|
+
return out.join("\n");
|
|
1330
|
+
}
|
|
1331
|
+
function highlightCode(code, lang) {
|
|
1332
|
+
if (!lang || !lowlight.registered(lang)) return styleEachLine(code, kleur.yellow);
|
|
1333
|
+
return renderTree(lowlight.highlight(lang, code), (text) => text);
|
|
1334
|
+
}
|
|
1335
|
+
function renderTree(node, style) {
|
|
1336
|
+
if (node.type === "text") return styleEachLine(node.value, style);
|
|
1337
|
+
if (node.type !== "root" && node.type !== "element") return "";
|
|
1338
|
+
const own = node.type === "element" ? tokenStyle(node.properties["className"]) : void 0;
|
|
1339
|
+
const next = own ? (text) => style(own(text)) : style;
|
|
1340
|
+
return node.children.map((child) => renderTree(child, next)).join("");
|
|
1341
|
+
}
|
|
1342
|
+
function tokenStyle(className) {
|
|
1343
|
+
if (!Array.isArray(className)) return void 0;
|
|
1344
|
+
for (const name of className) {
|
|
1345
|
+
if (typeof name !== "string" || !name.startsWith("hljs-")) continue;
|
|
1346
|
+
const style = TOKEN_STYLES[name.slice(5)];
|
|
1347
|
+
if (style) return style;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function styleEachLine(text, style) {
|
|
1351
|
+
return text.split("\n").map((line) => line ? style(line) : line).join("\n");
|
|
1352
|
+
}
|
|
1353
|
+
function highlightLine(line, nextLine) {
|
|
1354
|
+
const quote = line.match(QUOTE);
|
|
1355
|
+
if (quote) {
|
|
1356
|
+
const [whole, indent = ""] = quote;
|
|
1357
|
+
const rest = line.slice(whole.length);
|
|
1358
|
+
const gap = whole.endsWith(" ") ? " " : "";
|
|
1359
|
+
return `${indent}${kleur.dim(">")}${gap}${highlightLine(rest, stripPrefix(nextLine, whole))}`;
|
|
1360
|
+
}
|
|
1361
|
+
const list = line.match(LIST);
|
|
1362
|
+
if (list) {
|
|
1363
|
+
const [whole, indent = "", marker = "", checkbox] = list;
|
|
1364
|
+
const rest = line.slice(whole.length);
|
|
1365
|
+
const box = checkbox ? `${kleur.bold(checkbox.trim())} ` : "";
|
|
1366
|
+
return `${indent}${kleur.dim(marker)} ${box}${highlightLine(rest, void 0)}`;
|
|
1367
|
+
}
|
|
1368
|
+
if (HEADING.test(line)) return kleur.bold().cyan(line);
|
|
1369
|
+
if (RULE.test(line)) return kleur.dim(line);
|
|
1370
|
+
if (TABLE_SEPARATOR.test(line)) return kleur.dim(line);
|
|
1371
|
+
if (TABLE_ROW.test(line)) return tableRow(line, nextLine !== void 0 && TABLE_SEPARATOR.test(nextLine));
|
|
1372
|
+
const details = line.match(DETAILS_OPEN);
|
|
1373
|
+
if (details) {
|
|
1374
|
+
const [, indent = "", title = ""] = details;
|
|
1375
|
+
return `${indent}${kleur.dim("<details><summary>")}${highlightInline(title)}${kleur.dim("</summary>")}`;
|
|
1376
|
+
}
|
|
1377
|
+
if (DETAILS_CLOSE.test(line)) return kleur.dim(line);
|
|
1378
|
+
return highlightInline(line);
|
|
1379
|
+
}
|
|
1380
|
+
function stripPrefix(line, prefix) {
|
|
1381
|
+
return line?.startsWith(prefix) ? line.slice(prefix.length) : void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function tableRow(line, header) {
|
|
1384
|
+
const styled = line.split(CELL_DIVIDER).slice(1, -1).map((cell) => header ? kleur.bold(highlightInline(cell)) : highlightInline(cell));
|
|
1385
|
+
const pipe = kleur.dim("|");
|
|
1386
|
+
return `${pipe}${styled.join(pipe)}${pipe}`;
|
|
1387
|
+
}
|
|
1388
|
+
function highlightInline(text) {
|
|
1389
|
+
return text.replace(INLINE, (match, code, linkText, url, boldItalic, bold, italic, strike) => {
|
|
1390
|
+
if (code !== void 0) return kleur.yellow(code);
|
|
1391
|
+
if (linkText !== void 0 && url !== void 0) return link(linkText, url);
|
|
1392
|
+
if (boldItalic !== void 0) return kleur.bold().italic(match);
|
|
1393
|
+
if (bold !== void 0) return kleur.bold(`**${highlightInline(bold)}**`);
|
|
1394
|
+
if (italic !== void 0) return kleur.italic(`*${highlightInline(italic)}*`);
|
|
1395
|
+
if (strike !== void 0) return kleur.strikethrough(`~~${highlightInline(strike)}~~`);
|
|
1396
|
+
return match;
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
function link(text, url) {
|
|
1400
|
+
const open = kleur.dim("[");
|
|
1401
|
+
const middle = kleur.dim("](");
|
|
1402
|
+
return `${open}${highlightInline(text)}${middle}${kleur.dim().underline(url)}${kleur.dim(")")}`;
|
|
1403
|
+
}
|
|
1404
|
+
//#endregion
|
|
1405
|
+
//#region src/commands/view.ts
|
|
1406
|
+
function fieldLines(pairs) {
|
|
1407
|
+
const kept = pairs.filter(([, value]) => value !== "");
|
|
1408
|
+
const width = Math.max(...kept.map(([label]) => label.length)) + 1;
|
|
1409
|
+
return kept.map(([label, value]) => `${kleur.dim(`${label}:`.padEnd(width))} ${value}`);
|
|
1410
|
+
}
|
|
1411
|
+
function dateWithAge(iso, nowMs) {
|
|
1412
|
+
if (!iso) return "";
|
|
1413
|
+
return `${formatDateTime(iso).slice(0, 10)} (${relativeTime(iso, nowMs)})`;
|
|
1414
|
+
}
|
|
1415
|
+
function bodyLines(body) {
|
|
1416
|
+
const md = adfToMarkdown(body);
|
|
1417
|
+
return md ? ["", highlightMarkdown(md)] : [];
|
|
1418
|
+
}
|
|
1419
|
+
function commentSection(comments, allComments) {
|
|
1420
|
+
if (comments.length === 0) return [];
|
|
1421
|
+
const visible = allComments ? comments : comments.slice(-5);
|
|
1422
|
+
const heading = comments.length - visible.length > 0 ? `Comments (${comments.length}, showing last ${visible.length} — --all-comments for all)` : `Comments (${comments.length})`;
|
|
1423
|
+
const lines = ["", kleur.bold(heading)];
|
|
1424
|
+
for (const comment of visible) {
|
|
1425
|
+
const body = adfToMarkdown(comment.body);
|
|
1426
|
+
lines.push("", commentHeader(comment), ...body ? [highlightMarkdown(body)] : []);
|
|
1427
|
+
}
|
|
1428
|
+
return lines;
|
|
1429
|
+
}
|
|
1430
|
+
function commentHeader(comment) {
|
|
1431
|
+
return `─ ${kleur.bold(comment.author || "Unknown")}${comment.created ? ` · ${kleur.dim(formatDateTime(comment.created))}` : ""}`;
|
|
1432
|
+
}
|
|
1433
|
+
function attachmentSection(attachments) {
|
|
1434
|
+
if (attachments.length === 0) return [];
|
|
1435
|
+
return [
|
|
1436
|
+
"",
|
|
1437
|
+
kleur.bold("Attachments"),
|
|
1438
|
+
...attachments.map((a) => `- ${a.filename}`)
|
|
1439
|
+
];
|
|
1440
|
+
}
|
|
1441
|
+
//#endregion
|
|
1243
1442
|
//#region src/api/attachments.ts
|
|
1244
1443
|
function mediaResolver(downloaded) {
|
|
1245
1444
|
const byMediaId = new Map(downloaded.map((d) => [d.mediaId, d.relativePath]));
|
|
@@ -1734,7 +1933,79 @@ async function runPlan(plan, options, push) {
|
|
|
1734
1933
|
await push();
|
|
1735
1934
|
}
|
|
1736
1935
|
//#endregion
|
|
1936
|
+
//#region src/util/pager.ts
|
|
1937
|
+
const DEFAULT_ROWS = 24;
|
|
1938
|
+
const DEFAULT_COLUMNS = 80;
|
|
1939
|
+
const DEFAULT_PAGER = "less";
|
|
1940
|
+
const DEFAULT_LESS = "FRX";
|
|
1941
|
+
function renderedHeight(text, columns) {
|
|
1942
|
+
const width = Math.max(1, columns);
|
|
1943
|
+
return text.split("\n").map((line) => Math.max(1, Math.ceil(stripVTControlCharacters(line).length / width))).reduce((sum, rows) => sum + rows, 0);
|
|
1944
|
+
}
|
|
1945
|
+
function shouldPage(text, term) {
|
|
1946
|
+
if (!term.isTTY) return false;
|
|
1947
|
+
const rows = term.rows ?? DEFAULT_ROWS;
|
|
1948
|
+
return renderedHeight(text, term.columns ?? DEFAULT_COLUMNS) > rows;
|
|
1949
|
+
}
|
|
1950
|
+
function pagerCommand(env) {
|
|
1951
|
+
const [command = DEFAULT_PAGER, ...args] = (env["PAGER"] ?? "").trim().split(/\s+/).filter(Boolean);
|
|
1952
|
+
return {
|
|
1953
|
+
command,
|
|
1954
|
+
args,
|
|
1955
|
+
env: {
|
|
1956
|
+
...env,
|
|
1957
|
+
LESS: env["LESS"] ?? DEFAULT_LESS
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
async function printPaged(text, options = {}) {
|
|
1962
|
+
const term = options.term ?? process.stdout;
|
|
1963
|
+
if (options.pager === false || !shouldPage(text, term)) {
|
|
1964
|
+
console.log(text);
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
if (!await pipeToPager(text, pagerCommand(options.env ?? process.env))) console.log(text);
|
|
1968
|
+
}
|
|
1969
|
+
function pipeToPager(text, pager) {
|
|
1970
|
+
return new Promise((resolve) => {
|
|
1971
|
+
const child = spawn(pager.command, pager.args, {
|
|
1972
|
+
stdio: [
|
|
1973
|
+
"pipe",
|
|
1974
|
+
"inherit",
|
|
1975
|
+
"inherit"
|
|
1976
|
+
],
|
|
1977
|
+
env: pager.env
|
|
1978
|
+
});
|
|
1979
|
+
child.once("error", () => resolve(false));
|
|
1980
|
+
child.once("close", () => resolve(true));
|
|
1981
|
+
child.stdin.once("error", () => {});
|
|
1982
|
+
child.stdin.end(`${text}\n`);
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
//#endregion
|
|
1737
1986
|
//#region src/commands/confluence.ts
|
|
1987
|
+
async function confluenceView(arg, options) {
|
|
1988
|
+
const auth = await requireAuth();
|
|
1989
|
+
const id = await resolveRef(arg, PAGE_REF);
|
|
1990
|
+
await printPaged(formatPageView(await fetchPage(new AtlassianClient(auth), auth.site, id), Date.now(), options.allComments ?? false).join("\n"), { pager: options.pager });
|
|
1991
|
+
}
|
|
1992
|
+
function formatPageView(page, nowMs, allComments) {
|
|
1993
|
+
return [
|
|
1994
|
+
kleur.bold(page.title),
|
|
1995
|
+
...fieldLines([
|
|
1996
|
+
["Space", page.spaceKey],
|
|
1997
|
+
["ID", page.id],
|
|
1998
|
+
["Version", page.version ? String(page.version) : ""],
|
|
1999
|
+
["Author", page.author],
|
|
2000
|
+
["Created", dateWithAge(page.createdAt, nowMs)],
|
|
2001
|
+
["Updated", dateWithAge(page.updatedAt, nowMs)],
|
|
2002
|
+
["URL", page.url]
|
|
2003
|
+
]),
|
|
2004
|
+
...bodyLines(page.body),
|
|
2005
|
+
...commentSection(page.comments, allComments),
|
|
2006
|
+
...attachmentSection(page.attachments)
|
|
2007
|
+
];
|
|
2008
|
+
}
|
|
1738
2009
|
async function confluenceCopy(arg, options) {
|
|
1739
2010
|
const auth = await requireAuth();
|
|
1740
2011
|
const id = await resolveRef(arg, PAGE_REF);
|
|
@@ -1786,37 +2057,59 @@ async function fileSize(path) {
|
|
|
1786
2057
|
}
|
|
1787
2058
|
async function confluenceSearch(query, options) {
|
|
1788
2059
|
if (options.cql && (query || options.space)) throw new Error("--cql cannot be combined with a text query or --space.");
|
|
2060
|
+
await listPages({
|
|
2061
|
+
text: query,
|
|
2062
|
+
space: options.space,
|
|
2063
|
+
cql: options.cql
|
|
2064
|
+
}, "No matching pages.", options);
|
|
2065
|
+
}
|
|
2066
|
+
async function confluenceList(options) {
|
|
2067
|
+
await listPages({
|
|
2068
|
+
starred: true,
|
|
2069
|
+
space: options.space
|
|
2070
|
+
}, options.space ? `No starred pages in ${options.space}.` : "No starred pages.", options);
|
|
2071
|
+
}
|
|
2072
|
+
async function listPages(filter, empty, options) {
|
|
1789
2073
|
if (options.json && options.copy) throw new Error("--json and --copy cannot be used together.");
|
|
1790
2074
|
const auth = await requireAuth();
|
|
1791
2075
|
const client = new AtlassianClient(auth);
|
|
1792
2076
|
const limit = parseLimit(options.limit);
|
|
1793
2077
|
const { pages, hasMore } = await searchPages(client, auth.site, {
|
|
1794
|
-
|
|
1795
|
-
space: options.space,
|
|
1796
|
-
cql: options.cql,
|
|
2078
|
+
...filter,
|
|
1797
2079
|
limit
|
|
1798
2080
|
});
|
|
1799
|
-
await runSearch(pages.
|
|
1800
|
-
id: p.id,
|
|
1801
|
-
fixedColumns: `${p.id} ${p.space}`,
|
|
1802
|
-
freeText: p.title,
|
|
1803
|
-
json: {
|
|
1804
|
-
id: p.id,
|
|
1805
|
-
space: p.space,
|
|
1806
|
-
title: p.title,
|
|
1807
|
-
url: p.url
|
|
1808
|
-
}
|
|
1809
|
-
})), {
|
|
2081
|
+
await runSearch(formatPageRows(pages, Date.now()), {
|
|
1810
2082
|
json: options.json,
|
|
1811
2083
|
copy: options.copy,
|
|
1812
2084
|
out: options.out,
|
|
1813
|
-
empty
|
|
2085
|
+
empty,
|
|
1814
2086
|
footer: hasMore ? searchFooter(limit) : void 0
|
|
1815
|
-
},
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2087
|
+
}, PAGE_NOUN, (id) => copyPage(client, auth.site, id, options.out));
|
|
2088
|
+
}
|
|
2089
|
+
function formatPageRows(pages, nowMs) {
|
|
2090
|
+
return alignedRows(pages, nowMs, (p) => ({
|
|
2091
|
+
id: p.id,
|
|
2092
|
+
label: p.space,
|
|
2093
|
+
color: colorForSpace(p.space),
|
|
2094
|
+
text: p.title
|
|
2095
|
+
}));
|
|
1819
2096
|
}
|
|
2097
|
+
const SPACE_COLORS = [
|
|
2098
|
+
kleur.cyan,
|
|
2099
|
+
kleur.yellow,
|
|
2100
|
+
kleur.green,
|
|
2101
|
+
kleur.magenta,
|
|
2102
|
+
kleur.blue
|
|
2103
|
+
];
|
|
2104
|
+
function colorForSpace(space) {
|
|
2105
|
+
let hash = 0;
|
|
2106
|
+
for (const char of space) hash = hash * 31 + (char.codePointAt(0) ?? 0) >>> 0;
|
|
2107
|
+
return SPACE_COLORS[hash % SPACE_COLORS.length] ?? kleur.cyan;
|
|
2108
|
+
}
|
|
2109
|
+
const PAGE_NOUN = {
|
|
2110
|
+
singular: "page",
|
|
2111
|
+
plural: "pages"
|
|
2112
|
+
};
|
|
1820
2113
|
async function copyPage(client, site, id, out) {
|
|
1821
2114
|
console.log(`Fetching page ${id} ...`);
|
|
1822
2115
|
await runCopy(planPageCopy(await fetchPage(client, site, id), out), (url) => client.getBinary(url));
|
|
@@ -1830,8 +2123,10 @@ const PAGE_REF = {
|
|
|
1830
2123
|
//#region src/cli/confluence.ts
|
|
1831
2124
|
function registerConfluence(confluence) {
|
|
1832
2125
|
confluence.description("Confluence commands");
|
|
2126
|
+
confluence.command("view [page]").description("Show a Confluence page (id or URL) in the terminal").option("--all-comments", "show all comments instead of the last 5").option("--no-pager", "print directly instead of paging long output").action(run(confluenceView));
|
|
1833
2127
|
confluence.command("copy [page]").description("Copy a Confluence page (id or URL) to a Markdown file").option("-o, --out <path>", "output file or directory").action(run(confluenceCopy));
|
|
1834
2128
|
confluence.command("update [file]").description("Update a Confluence page from an edited Markdown file").option("--title", "also push the H1 as the page title").option("-m, --message <text>", "version message (default 'Updated via atlass')").option("-f, --force", "skip the stale-version and data-loss checks").option("--dry-run", "show what would change without writing").action(run(confluenceUpdate));
|
|
2129
|
+
confluence.command("list").description("List pages you starred").option("-s, --space <key>", "limit to a space").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").option("-c, --copy", "pick results to copy to Markdown").option("-o, --out <dir>", "output directory for --copy").action(run(confluenceList));
|
|
1835
2130
|
confluence.command("search [query]").description("Search Confluence pages (text query, --space, or --cql)").option("-s, --space <key>", "limit to a space").option("--cql <cql>", "raw CQL query (ignores other filters)").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").option("-c, --copy", "pick results to copy to Markdown").option("-o, --out <dir>", "output directory for --copy").action(run(confluenceSearch));
|
|
1836
2131
|
return confluence;
|
|
1837
2132
|
}
|
|
@@ -2034,148 +2329,6 @@ async function fetchComments(client, key) {
|
|
|
2034
2329
|
}));
|
|
2035
2330
|
}
|
|
2036
2331
|
//#endregion
|
|
2037
|
-
//#region src/markdown/highlight.ts
|
|
2038
|
-
const HEADING = /^#{1,6} /;
|
|
2039
|
-
const QUOTE = /^(\s*)>( |$)/;
|
|
2040
|
-
const LIST = /^(\s*)(-|\d+\.) (\[[ x]\] )?/;
|
|
2041
|
-
const RULE = /^\s*---$/;
|
|
2042
|
-
const TABLE_ROW = /^\|.*\|$/;
|
|
2043
|
-
const TABLE_SEPARATOR = /^\|( --- \|)+$/;
|
|
2044
|
-
const CELL_DIVIDER = /(?<!\\)\|/;
|
|
2045
|
-
const DETAILS_OPEN = /^(\s*)<details><summary>(.*)<\/summary>$/;
|
|
2046
|
-
const DETAILS_CLOSE = /^\s*<\/details>$/;
|
|
2047
|
-
const FENCE_OPEN = /^((?:\s|> |- |\d+\. )*)```(\S*)$/;
|
|
2048
|
-
const FENCE = "```";
|
|
2049
|
-
const INLINE = /(`[^`]+`)|\[([^\]]+)\]\(((?:[^()]|\([^()]*\))+)\)|\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|\*(?!\*)(.+?)(?<!\*)\*(?!\*)|~~(.+?)~~/g;
|
|
2050
|
-
const TOKEN_STYLES = {
|
|
2051
|
-
keyword: kleur.magenta,
|
|
2052
|
-
built_in: kleur.magenta,
|
|
2053
|
-
literal: kleur.magenta,
|
|
2054
|
-
string: kleur.green,
|
|
2055
|
-
regexp: kleur.green,
|
|
2056
|
-
addition: kleur.green,
|
|
2057
|
-
number: kleur.yellow,
|
|
2058
|
-
symbol: kleur.yellow,
|
|
2059
|
-
meta: kleur.yellow,
|
|
2060
|
-
attr: kleur.yellow,
|
|
2061
|
-
comment: kleur.dim,
|
|
2062
|
-
quote: kleur.dim,
|
|
2063
|
-
deletion: kleur.dim,
|
|
2064
|
-
title: kleur.cyan,
|
|
2065
|
-
type: kleur.cyan,
|
|
2066
|
-
section: kleur.cyan
|
|
2067
|
-
};
|
|
2068
|
-
const lowlight = createLowlight(common);
|
|
2069
|
-
function highlightMarkdown(md) {
|
|
2070
|
-
const lines = md.split("\n");
|
|
2071
|
-
const out = [];
|
|
2072
|
-
for (let i = 0; i < lines.length; i++) {
|
|
2073
|
-
const line = lines[i] ?? "";
|
|
2074
|
-
const fence = line.match(FENCE_OPEN);
|
|
2075
|
-
if (!fence) {
|
|
2076
|
-
out.push(highlightLine(line, lines[i + 1]));
|
|
2077
|
-
continue;
|
|
2078
|
-
}
|
|
2079
|
-
const [, prefix = "", lang = ""] = fence;
|
|
2080
|
-
const styledPrefix = prefix.replace(/\S+/g, (marker) => kleur.dim(marker));
|
|
2081
|
-
out.push(`${styledPrefix}${kleur.dim(`${FENCE}${lang}`)}`);
|
|
2082
|
-
const code = [];
|
|
2083
|
-
const prefixed = [];
|
|
2084
|
-
let j = i + 1;
|
|
2085
|
-
for (; j < lines.length; j++) {
|
|
2086
|
-
const raw = lines[j] ?? "";
|
|
2087
|
-
const hasPrefix = raw.startsWith(prefix) || raw === prefix.trimEnd();
|
|
2088
|
-
const body = raw.startsWith(prefix) ? raw.slice(prefix.length) : hasPrefix ? "" : raw;
|
|
2089
|
-
if (body === FENCE) break;
|
|
2090
|
-
code.push(body);
|
|
2091
|
-
prefixed.push(hasPrefix);
|
|
2092
|
-
}
|
|
2093
|
-
if (code.length > 0) highlightCode(code.join("\n"), lang).split("\n").forEach((codeLine, k) => {
|
|
2094
|
-
const lead = prefixed[k] ? styledPrefix : "";
|
|
2095
|
-
out.push(codeLine ? `${lead}${codeLine}` : lead.trimEnd());
|
|
2096
|
-
});
|
|
2097
|
-
if (j < lines.length) {
|
|
2098
|
-
const lead = (lines[j] ?? "").startsWith(prefix) ? styledPrefix : "";
|
|
2099
|
-
out.push(`${lead}${kleur.dim(FENCE)}`);
|
|
2100
|
-
}
|
|
2101
|
-
i = j;
|
|
2102
|
-
}
|
|
2103
|
-
return out.join("\n");
|
|
2104
|
-
}
|
|
2105
|
-
function highlightCode(code, lang) {
|
|
2106
|
-
if (!lang || !lowlight.registered(lang)) return styleEachLine(code, kleur.yellow);
|
|
2107
|
-
return renderTree(lowlight.highlight(lang, code), (text) => text);
|
|
2108
|
-
}
|
|
2109
|
-
function renderTree(node, style) {
|
|
2110
|
-
if (node.type === "text") return styleEachLine(node.value, style);
|
|
2111
|
-
if (node.type !== "root" && node.type !== "element") return "";
|
|
2112
|
-
const own = node.type === "element" ? tokenStyle(node.properties["className"]) : void 0;
|
|
2113
|
-
const next = own ? (text) => style(own(text)) : style;
|
|
2114
|
-
return node.children.map((child) => renderTree(child, next)).join("");
|
|
2115
|
-
}
|
|
2116
|
-
function tokenStyle(className) {
|
|
2117
|
-
if (!Array.isArray(className)) return void 0;
|
|
2118
|
-
for (const name of className) {
|
|
2119
|
-
if (typeof name !== "string" || !name.startsWith("hljs-")) continue;
|
|
2120
|
-
const style = TOKEN_STYLES[name.slice(5)];
|
|
2121
|
-
if (style) return style;
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
function styleEachLine(text, style) {
|
|
2125
|
-
return text.split("\n").map((line) => line ? style(line) : line).join("\n");
|
|
2126
|
-
}
|
|
2127
|
-
function highlightLine(line, nextLine) {
|
|
2128
|
-
const quote = line.match(QUOTE);
|
|
2129
|
-
if (quote) {
|
|
2130
|
-
const [whole, indent = ""] = quote;
|
|
2131
|
-
const rest = line.slice(whole.length);
|
|
2132
|
-
const gap = whole.endsWith(" ") ? " " : "";
|
|
2133
|
-
return `${indent}${kleur.dim(">")}${gap}${highlightLine(rest, stripPrefix(nextLine, whole))}`;
|
|
2134
|
-
}
|
|
2135
|
-
const list = line.match(LIST);
|
|
2136
|
-
if (list) {
|
|
2137
|
-
const [whole, indent = "", marker = "", checkbox] = list;
|
|
2138
|
-
const rest = line.slice(whole.length);
|
|
2139
|
-
const box = checkbox ? `${kleur.bold(checkbox.trim())} ` : "";
|
|
2140
|
-
return `${indent}${kleur.dim(marker)} ${box}${highlightLine(rest, void 0)}`;
|
|
2141
|
-
}
|
|
2142
|
-
if (HEADING.test(line)) return kleur.bold().cyan(line);
|
|
2143
|
-
if (RULE.test(line)) return kleur.dim(line);
|
|
2144
|
-
if (TABLE_SEPARATOR.test(line)) return kleur.dim(line);
|
|
2145
|
-
if (TABLE_ROW.test(line)) return tableRow(line, nextLine !== void 0 && TABLE_SEPARATOR.test(nextLine));
|
|
2146
|
-
const details = line.match(DETAILS_OPEN);
|
|
2147
|
-
if (details) {
|
|
2148
|
-
const [, indent = "", title = ""] = details;
|
|
2149
|
-
return `${indent}${kleur.dim("<details><summary>")}${highlightInline(title)}${kleur.dim("</summary>")}`;
|
|
2150
|
-
}
|
|
2151
|
-
if (DETAILS_CLOSE.test(line)) return kleur.dim(line);
|
|
2152
|
-
return highlightInline(line);
|
|
2153
|
-
}
|
|
2154
|
-
function stripPrefix(line, prefix) {
|
|
2155
|
-
return line?.startsWith(prefix) ? line.slice(prefix.length) : void 0;
|
|
2156
|
-
}
|
|
2157
|
-
function tableRow(line, header) {
|
|
2158
|
-
const styled = line.split(CELL_DIVIDER).slice(1, -1).map((cell) => header ? kleur.bold(highlightInline(cell)) : highlightInline(cell));
|
|
2159
|
-
const pipe = kleur.dim("|");
|
|
2160
|
-
return `${pipe}${styled.join(pipe)}${pipe}`;
|
|
2161
|
-
}
|
|
2162
|
-
function highlightInline(text) {
|
|
2163
|
-
return text.replace(INLINE, (match, code, linkText, url, boldItalic, bold, italic, strike) => {
|
|
2164
|
-
if (code !== void 0) return kleur.yellow(code);
|
|
2165
|
-
if (linkText !== void 0 && url !== void 0) return link(linkText, url);
|
|
2166
|
-
if (boldItalic !== void 0) return kleur.bold().italic(match);
|
|
2167
|
-
if (bold !== void 0) return kleur.bold(`**${highlightInline(bold)}**`);
|
|
2168
|
-
if (italic !== void 0) return kleur.italic(`*${highlightInline(italic)}*`);
|
|
2169
|
-
if (strike !== void 0) return kleur.strikethrough(`~~${highlightInline(strike)}~~`);
|
|
2170
|
-
return match;
|
|
2171
|
-
});
|
|
2172
|
-
}
|
|
2173
|
-
function link(text, url) {
|
|
2174
|
-
const open = kleur.dim("[");
|
|
2175
|
-
const middle = kleur.dim("](");
|
|
2176
|
-
return `${open}${highlightInline(text)}${middle}${kleur.dim().underline(url)}${kleur.dim(")")}`;
|
|
2177
|
-
}
|
|
2178
|
-
//#endregion
|
|
2179
2332
|
//#region src/commands/jira.ts
|
|
2180
2333
|
async function jiraProjects(query, options) {
|
|
2181
2334
|
const auth = await requireAuth();
|
|
@@ -2217,8 +2370,7 @@ function formatStatusRows(statuses) {
|
|
|
2217
2370
|
async function jiraView(arg, options) {
|
|
2218
2371
|
const auth = await requireAuth();
|
|
2219
2372
|
const key = await resolveRef(arg, ISSUE_REF);
|
|
2220
|
-
|
|
2221
|
-
for (const line of formatIssueView(issue, Date.now(), options.allComments ?? false)) console.log(line);
|
|
2373
|
+
await printPaged(formatIssueView(await fetchIssue(new AtlassianClient(auth), auth.site, key), Date.now(), options.allComments ?? false).join("\n"), { pager: options.pager });
|
|
2222
2374
|
}
|
|
2223
2375
|
const CATEGORY_COLORS = {
|
|
2224
2376
|
new: kleur.cyan,
|
|
@@ -2229,59 +2381,25 @@ function colorForCategory(category) {
|
|
|
2229
2381
|
return CATEGORY_COLORS[category] ?? ((text) => text);
|
|
2230
2382
|
}
|
|
2231
2383
|
function formatIssueView(issue, nowMs, allComments) {
|
|
2384
|
+
const colorStatus = colorForCategory(issue.statusCategory);
|
|
2232
2385
|
return [
|
|
2233
2386
|
`${kleur.bold(issue.key)} ${issue.summary}`,
|
|
2234
|
-
...fieldLines(
|
|
2235
|
-
|
|
2387
|
+
...fieldLines([
|
|
2388
|
+
["Type", issue.type],
|
|
2389
|
+
["Status", issue.status && colorStatus(issue.status)],
|
|
2390
|
+
["Assignee", issue.assignee],
|
|
2391
|
+
["Reporter", issue.reporter],
|
|
2392
|
+
["Priority", issue.priority],
|
|
2393
|
+
["Labels", issue.labels.join(", ")],
|
|
2394
|
+
["Created", dateWithAge(issue.created, nowMs)],
|
|
2395
|
+
["Updated", dateWithAge(issue.updated, nowMs)],
|
|
2396
|
+
["URL", issue.url]
|
|
2397
|
+
]),
|
|
2398
|
+
...bodyLines(issue.description),
|
|
2236
2399
|
...commentSection(issue.comments, allComments),
|
|
2237
2400
|
...attachmentSection(issue.attachments)
|
|
2238
2401
|
];
|
|
2239
2402
|
}
|
|
2240
|
-
function fieldLines(issue, nowMs) {
|
|
2241
|
-
const colorStatus = colorForCategory(issue.statusCategory);
|
|
2242
|
-
const kept = [
|
|
2243
|
-
["Type", issue.type],
|
|
2244
|
-
["Status", issue.status && colorStatus(issue.status)],
|
|
2245
|
-
["Assignee", issue.assignee],
|
|
2246
|
-
["Reporter", issue.reporter],
|
|
2247
|
-
["Priority", issue.priority],
|
|
2248
|
-
["Labels", issue.labels.join(", ")],
|
|
2249
|
-
["Created", dateWithAge(issue.created, nowMs)],
|
|
2250
|
-
["Updated", dateWithAge(issue.updated, nowMs)],
|
|
2251
|
-
["URL", issue.url]
|
|
2252
|
-
].filter(([, value]) => value !== "");
|
|
2253
|
-
const width = Math.max(...kept.map(([label]) => label.length)) + 1;
|
|
2254
|
-
return kept.map(([label, value]) => `${kleur.dim(`${label}:`.padEnd(width))} ${value}`);
|
|
2255
|
-
}
|
|
2256
|
-
function dateWithAge(iso, nowMs) {
|
|
2257
|
-
if (!iso) return "";
|
|
2258
|
-
return `${formatDateTime(iso).slice(0, 10)} (${relativeTime(iso, nowMs)})`;
|
|
2259
|
-
}
|
|
2260
|
-
function bodyLines(body) {
|
|
2261
|
-
return body ? ["", highlightMarkdown(body)] : [];
|
|
2262
|
-
}
|
|
2263
|
-
function commentSection(comments, allComments) {
|
|
2264
|
-
if (comments.length === 0) return [];
|
|
2265
|
-
const visible = allComments ? comments : comments.slice(-5);
|
|
2266
|
-
const heading = comments.length - visible.length > 0 ? `Comments (${comments.length}, showing last ${visible.length} — --all-comments for all)` : `Comments (${comments.length})`;
|
|
2267
|
-
const lines = ["", kleur.bold(heading)];
|
|
2268
|
-
for (const comment of visible) {
|
|
2269
|
-
const body = adfToMarkdown(comment.body);
|
|
2270
|
-
lines.push("", commentHeader(comment), ...body ? [highlightMarkdown(body)] : []);
|
|
2271
|
-
}
|
|
2272
|
-
return lines;
|
|
2273
|
-
}
|
|
2274
|
-
function commentHeader(comment) {
|
|
2275
|
-
return `─ ${kleur.bold(comment.author || "Unknown")}${comment.created ? ` · ${kleur.dim(formatDateTime(comment.created))}` : ""}`;
|
|
2276
|
-
}
|
|
2277
|
-
function attachmentSection(attachments) {
|
|
2278
|
-
if (attachments.length === 0) return [];
|
|
2279
|
-
return [
|
|
2280
|
-
"",
|
|
2281
|
-
kleur.bold("Attachments"),
|
|
2282
|
-
...attachments.map((a) => `- ${a.filename}`)
|
|
2283
|
-
];
|
|
2284
|
-
}
|
|
2285
2403
|
async function jiraCopy(arg, options) {
|
|
2286
2404
|
const auth = await requireAuth();
|
|
2287
2405
|
const key = await resolveRef(arg, ISSUE_REF);
|
|
@@ -2318,17 +2436,7 @@ async function jiraSearch(query, options) {
|
|
|
2318
2436
|
jql: options.jql,
|
|
2319
2437
|
limit
|
|
2320
2438
|
});
|
|
2321
|
-
await runSearch(issues.
|
|
2322
|
-
id: i.key,
|
|
2323
|
-
fixedColumns: `${i.key} ${i.status}`,
|
|
2324
|
-
freeText: i.summary,
|
|
2325
|
-
json: {
|
|
2326
|
-
key: i.key,
|
|
2327
|
-
status: i.status,
|
|
2328
|
-
summary: i.summary,
|
|
2329
|
-
url: i.url
|
|
2330
|
-
}
|
|
2331
|
-
})), {
|
|
2439
|
+
await runSearch(formatIssueRows(issues, Date.now()), {
|
|
2332
2440
|
json: options.json,
|
|
2333
2441
|
copy: options.copy,
|
|
2334
2442
|
out: options.out,
|
|
@@ -2344,7 +2452,7 @@ async function jiraList(options) {
|
|
|
2344
2452
|
all: options.all,
|
|
2345
2453
|
project: options.project
|
|
2346
2454
|
});
|
|
2347
|
-
await runSearch(
|
|
2455
|
+
await runSearch(formatIssueRows(sortByCategoryThenUpdated(issues), Date.now()), {
|
|
2348
2456
|
json: options.json,
|
|
2349
2457
|
copy: options.copy,
|
|
2350
2458
|
out: options.out,
|
|
@@ -2352,21 +2460,13 @@ async function jiraList(options) {
|
|
|
2352
2460
|
footer: truncated ? `\nShowing the first ${issues.length}; narrow with --project.` : void 0
|
|
2353
2461
|
}, ISSUE_NOUN, (key) => copyIssue(client, auth.site, key, options.out));
|
|
2354
2462
|
}
|
|
2355
|
-
function
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
const status = colorForCategory(i.statusCategory)(i.status.padEnd(statusWidth));
|
|
2363
|
-
return {
|
|
2364
|
-
id: i.key,
|
|
2365
|
-
fixedColumns: `${i.key.padEnd(keyWidth)} ${status} ${(ages[n] ?? "").padEnd(ageWidth)}`,
|
|
2366
|
-
freeText: i.summary,
|
|
2367
|
-
json: { ...i }
|
|
2368
|
-
};
|
|
2369
|
-
});
|
|
2463
|
+
function formatIssueRows(issues, nowMs) {
|
|
2464
|
+
return alignedRows(issues, nowMs, (i) => ({
|
|
2465
|
+
id: i.key,
|
|
2466
|
+
label: i.status,
|
|
2467
|
+
color: colorForCategory(i.statusCategory),
|
|
2468
|
+
text: i.summary
|
|
2469
|
+
}));
|
|
2370
2470
|
}
|
|
2371
2471
|
const ISSUE_NOUN = {
|
|
2372
2472
|
singular: "issue",
|
|
@@ -2387,7 +2487,7 @@ function registerJira(jira) {
|
|
|
2387
2487
|
jira.description("Jira commands");
|
|
2388
2488
|
jira.command("projects [query]").description("List projects (optionally filtered by key or name)").option("--json", "output results as JSON").action(run(jiraProjects));
|
|
2389
2489
|
jira.command("statuses [query]").description("List statuses (optionally filtered by name, scoped with --project)").option("-p, --project <key>", "limit to statuses used by a project").option("--json", "output results as JSON").action(run(jiraStatuses));
|
|
2390
|
-
jira.command("view [issue]").description("Show a Jira issue (key or URL) in the terminal").option("--all-comments", "show all comments instead of the last 5").action(run(jiraView));
|
|
2490
|
+
jira.command("view [issue]").description("Show a Jira issue (key or URL) in the terminal").option("--all-comments", "show all comments instead of the last 5").option("--no-pager", "print directly instead of paging long output").action(run(jiraView));
|
|
2391
2491
|
jira.command("copy [issue]").description("Copy a Jira issue (key or URL) to a Markdown file").option("-o, --out <path>", "output file or directory").action(run(jiraCopy));
|
|
2392
2492
|
jira.command("update [file]").description("Update a Jira issue description from an edited Markdown file").option("--summary", "also push the H1 as the issue summary").option("-f, --force", "skip the stale-issue and data-loss checks").option("--dry-run", "show what would change without writing").action(run(jiraUpdate));
|
|
2393
2493
|
jira.command("list").description("List open issues assigned to you").option("-p, --project <key>", "limit to a project").option("-a, --all", "include Done issues updated in the last 30 days").option("--json", "output results as JSON").option("-c, --copy", "pick results to copy to Markdown").option("-o, --out <dir>", "output directory for --copy").action(run(jiraList));
|
|
@@ -2396,7 +2496,7 @@ function registerJira(jira) {
|
|
|
2396
2496
|
}
|
|
2397
2497
|
//#endregion
|
|
2398
2498
|
//#region package.json
|
|
2399
|
-
var version = "1.
|
|
2499
|
+
var version = "1.10.0";
|
|
2400
2500
|
//#endregion
|
|
2401
2501
|
//#region src/cli/build.ts
|
|
2402
2502
|
const PRODUCTS = {
|
package/dist/cli.mjs
CHANGED
package/dist/confluence.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as buildStandalone, r as fail } from "./build-
|
|
2
|
+
import { n as buildStandalone, r as fail } from "./build-Colq1d4N.mjs";
|
|
3
3
|
//#region src/confluence.ts
|
|
4
4
|
buildStandalone("confluence").parseAsync().catch(fail);
|
|
5
5
|
//#endregion
|
package/dist/jira.mjs
CHANGED