atlass 1.4.0 → 1.5.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 -0
- package/dist/cli.mjs +74 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -176,6 +176,23 @@ A discovery aid for the `--project` filter above: it fetches every project
|
|
|
176
176
|
list. An optional query filters by key or name server-side. `--json` emits
|
|
177
177
|
`{ key, name, id, type, url }` per project.
|
|
178
178
|
|
|
179
|
+
### List Jira statuses
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
atlass jira statuses # every status on the site
|
|
183
|
+
atlass jira statuses progress # filter by name
|
|
184
|
+
atlass jira statuses --project PROJ # statuses used by one project
|
|
185
|
+
atlass jira statuses --json # machine output
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
A discovery aid for the `--status` filter above. Without `--project` it lists
|
|
189
|
+
every status on the site; with `--project` it lists the statuses that project's
|
|
190
|
+
issue types use (flattened to one list). An optional query filters by name
|
|
191
|
+
(case-insensitive substring). Statuses are collapsed by name and category, then
|
|
192
|
+
ordered by workflow lifecycle (To Do, then In Progress, then Done) and name.
|
|
193
|
+
Prints an aligned `Name Category` list; `--json` emits
|
|
194
|
+
`{ name, id, category, categoryKey }` per status.
|
|
195
|
+
|
|
179
196
|
### Search Confluence pages
|
|
180
197
|
|
|
181
198
|
```bash
|
package/dist/cli.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { Entry } from "@napi-rs/keyring";
|
|
|
8
8
|
import { marked } from "marked";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "1.
|
|
11
|
+
var version = "1.5.0";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/api/client.ts
|
|
14
14
|
var AtlassianClient = class {
|
|
@@ -76,20 +76,21 @@ var AtlassianClient = class {
|
|
|
76
76
|
return new Uint8Array(await res.arrayBuffer());
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
if (status === 413) return /* @__PURE__ */ new Error("Payload too large (413): the page or an attachment exceeds the size limit.");
|
|
87
|
-
if (status === 400) {
|
|
88
|
-
const detail = extractError(body);
|
|
89
|
-
return /* @__PURE__ */ new Error(`Bad request (400): ${detail || path}`);
|
|
79
|
+
var HttpError = class extends Error {
|
|
80
|
+
status;
|
|
81
|
+
constructor(status, message) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.status = status;
|
|
84
|
+
this.name = "HttpError";
|
|
90
85
|
}
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
};
|
|
87
|
+
function httpError(status, path, body = "") {
|
|
88
|
+
if (status === 401 || status === 403) return new HttpError(status, "Authentication failed (401/403). Run `atlass auth login` to update your token.");
|
|
89
|
+
if (status === 404) return new HttpError(status, `Not found (404): ${path}`);
|
|
90
|
+
if (status === 409) return new HttpError(status, `Conflict (409): ${extractError(body) || "the page changed on the server"}`);
|
|
91
|
+
if (status === 413) return new HttpError(status, "Payload too large (413): the page or an attachment exceeds the size limit.");
|
|
92
|
+
if (status === 400) return new HttpError(status, `Bad request (400): ${extractError(body) || path}`);
|
|
93
|
+
return new HttpError(status, `Request failed (${status}): ${extractError(body) || path}`);
|
|
93
94
|
}
|
|
94
95
|
function extractError(body) {
|
|
95
96
|
if (!body) return "";
|
|
@@ -1338,6 +1339,44 @@ function projectSearchQuery(query, startAt) {
|
|
|
1338
1339
|
if (query) params.set("query", query);
|
|
1339
1340
|
return params.toString();
|
|
1340
1341
|
}
|
|
1342
|
+
async function listStatuses(client, project) {
|
|
1343
|
+
return dedupeAndSortStatuses((project ? await fetchProjectStatuses(client, project) : await client.getJson("/rest/api/3/status")).map(toStatusSummary));
|
|
1344
|
+
}
|
|
1345
|
+
async function fetchProjectStatuses(client, project) {
|
|
1346
|
+
let groups;
|
|
1347
|
+
try {
|
|
1348
|
+
groups = await client.getJson(`/rest/api/3/project/${encodeURIComponent(project)}/statuses`);
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
if (err instanceof HttpError && err.status === 404) throw new Error(`No project found with key "${project}".`);
|
|
1351
|
+
throw err;
|
|
1352
|
+
}
|
|
1353
|
+
return groups.flatMap((g) => g.statuses ?? []);
|
|
1354
|
+
}
|
|
1355
|
+
function toStatusSummary(s) {
|
|
1356
|
+
return {
|
|
1357
|
+
name: s.name,
|
|
1358
|
+
id: s.id,
|
|
1359
|
+
category: s.statusCategory?.name ?? "",
|
|
1360
|
+
categoryKey: s.statusCategory?.key ?? ""
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
const CATEGORY_ORDER = {
|
|
1364
|
+
new: 0,
|
|
1365
|
+
indeterminate: 1,
|
|
1366
|
+
done: 2
|
|
1367
|
+
};
|
|
1368
|
+
const CATEGORY_LAST = Object.keys(CATEGORY_ORDER).length;
|
|
1369
|
+
function dedupeAndSortStatuses(statuses) {
|
|
1370
|
+
const byNameCategory = /* @__PURE__ */ new Map();
|
|
1371
|
+
for (const s of statuses) {
|
|
1372
|
+
const key = `${s.name}\0${s.categoryKey}`;
|
|
1373
|
+
if (!byNameCategory.has(key)) byNameCategory.set(key, s);
|
|
1374
|
+
}
|
|
1375
|
+
return [...byNameCategory.values()].sort((a, b) => {
|
|
1376
|
+
const rank = (s) => CATEGORY_ORDER[s.categoryKey] ?? CATEGORY_LAST;
|
|
1377
|
+
return rank(a) - rank(b) || a.name.localeCompare(b.name);
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1341
1380
|
function jqlValue(value) {
|
|
1342
1381
|
return `"${value.replace(/(["\\])/g, "\\$1")}"`;
|
|
1343
1382
|
}
|
|
@@ -1367,6 +1406,26 @@ function formatProjectRows(projects) {
|
|
|
1367
1406
|
const width = Math.max(...projects.map((p) => p.key.length));
|
|
1368
1407
|
return projects.map((p) => `${p.key.padEnd(width)} ${p.name}`);
|
|
1369
1408
|
}
|
|
1409
|
+
async function jiraStatuses(query, options) {
|
|
1410
|
+
let statuses = await listStatuses(new AtlassianClient(await requireAuth()), options.project);
|
|
1411
|
+
if (query) {
|
|
1412
|
+
const needle = query.toLowerCase();
|
|
1413
|
+
statuses = statuses.filter((s) => s.name.toLowerCase().includes(needle));
|
|
1414
|
+
}
|
|
1415
|
+
if (options.json) {
|
|
1416
|
+
console.log(JSON.stringify(statuses, null, 2));
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
if (statuses.length === 0) {
|
|
1420
|
+
console.log("No matching statuses.");
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
for (const line of formatStatusRows(statuses)) console.log(line);
|
|
1424
|
+
}
|
|
1425
|
+
function formatStatusRows(statuses) {
|
|
1426
|
+
const width = Math.max(...statuses.map((s) => s.name.length));
|
|
1427
|
+
return statuses.map((s) => `${s.name.padEnd(width)} ${s.category}`);
|
|
1428
|
+
}
|
|
1370
1429
|
async function jiraCopy(arg, options) {
|
|
1371
1430
|
const auth = await requireAuth();
|
|
1372
1431
|
const key = await resolveKey(arg);
|
|
@@ -1518,6 +1577,7 @@ auth.command("logout").description("Remove stored credentials").action(run(logou
|
|
|
1518
1577
|
auth.command("status").description("Show the current login").action(run(status));
|
|
1519
1578
|
const jira = program.command("jira").description("Jira commands");
|
|
1520
1579
|
jira.command("projects [query]").description("List projects (optionally filtered by key or name)").option("--json", "output results as JSON").action(run(jiraProjects));
|
|
1580
|
+
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));
|
|
1521
1581
|
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));
|
|
1522
1582
|
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));
|
|
1523
1583
|
jira.command("search [query]").description("Search Jira issues (text query, filters, or --jql)").option("-p, --project <key>", "limit to a project").option("-a, --assignee <who>", "limit to an assignee (or 'me')").option("-s, --status <status>", "limit to a status").option("--jql <jql>", "raw JQL 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(jiraSearch));
|