atlass 1.3.0 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/dist/cli.mjs +51 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -163,6 +163,19 @@ filters. Prints one issue per line (`KEY status summary`); use `--json` for
163
163
  machine output. Only the first `--limit` results are shown (default 25, max
164
164
  100).
165
165
 
166
+ ### List Jira projects
167
+
168
+ ```bash
169
+ atlass jira projects # every project you can browse
170
+ atlass jira projects pay # filter by key or name
171
+ atlass jira projects --json # machine output
172
+ ```
173
+
174
+ A discovery aid for the `--project` filter above: it fetches every project
175
+ (paginated, ordered by key) and prints one per line as an aligned `KEY Name`
176
+ list. An optional query filters by key or name server-side. `--json` emits
177
+ `{ key, name, id, type, url }` per project.
178
+
166
179
  ### Search Confluence pages
167
180
 
168
181
  ```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.3.0";
11
+ var version = "1.4.0";
12
12
  //#endregion
13
13
  //#region src/api/client.ts
14
14
  var AtlassianClient = class {
@@ -1254,13 +1254,16 @@ const FIELDS = [
1254
1254
  "updated",
1255
1255
  "attachment"
1256
1256
  ].join(",");
1257
+ function browseUrl(site, key) {
1258
+ return `${site}/browse/${key}`;
1259
+ }
1257
1260
  async function fetchIssue(client, site, key) {
1258
1261
  const issue = await client.getJson(`/rest/api/3/issue/${encodeURIComponent(key)}?fields=${FIELDS}`);
1259
1262
  const comments = await fetchComments(client, key);
1260
1263
  const f = issue.fields;
1261
1264
  return {
1262
1265
  key: issue.key,
1263
- url: `${site}/browse/${issue.key}`,
1266
+ url: browseUrl(site, issue.key),
1264
1267
  summary: f.summary ?? "",
1265
1268
  type: f.issuetype?.name ?? "",
1266
1269
  status: f.status?.name ?? "",
@@ -1295,7 +1298,7 @@ async function searchIssues(client, site, params) {
1295
1298
  key: i.key,
1296
1299
  status: i.fields?.status?.name ?? "",
1297
1300
  summary: decodeEntities(i.fields?.summary ?? ""),
1298
- url: `${site}/browse/${i.key}`
1301
+ url: browseUrl(site, i.key)
1299
1302
  }));
1300
1303
  }
1301
1304
  function buildJql(params) {
@@ -1308,6 +1311,33 @@ function buildJql(params) {
1308
1311
  if (clauses.length === 0) clauses.push("updated >= -30d");
1309
1312
  return `${clauses.join(" AND ")} ORDER BY updated DESC`;
1310
1313
  }
1314
+ const PROJECT_PAGE_SIZE = 50;
1315
+ async function listProjects(client, site, query) {
1316
+ const projects = [];
1317
+ for (let startAt = 0;;) {
1318
+ const res = await client.getJson(`/rest/api/3/project/search?${projectSearchQuery(query, startAt)}`);
1319
+ const values = res.values ?? [];
1320
+ for (const p of values) projects.push({
1321
+ key: p.key,
1322
+ name: p.name,
1323
+ id: p.id,
1324
+ type: p.projectTypeKey ?? "",
1325
+ url: browseUrl(site, p.key)
1326
+ });
1327
+ if (res.isLast || values.length === 0) break;
1328
+ startAt += values.length;
1329
+ }
1330
+ return projects;
1331
+ }
1332
+ function projectSearchQuery(query, startAt) {
1333
+ const params = new URLSearchParams({
1334
+ orderBy: "key",
1335
+ maxResults: String(PROJECT_PAGE_SIZE),
1336
+ startAt: String(startAt)
1337
+ });
1338
+ if (query) params.set("query", query);
1339
+ return params.toString();
1340
+ }
1311
1341
  function jqlValue(value) {
1312
1342
  return `"${value.replace(/(["\\])/g, "\\$1")}"`;
1313
1343
  }
@@ -1320,6 +1350,23 @@ async function fetchComments(client, key) {
1320
1350
  }
1321
1351
  //#endregion
1322
1352
  //#region src/commands/jira.ts
1353
+ async function jiraProjects(query, options) {
1354
+ const auth = await requireAuth();
1355
+ const projects = await listProjects(new AtlassianClient(auth), auth.site, query);
1356
+ if (options.json) {
1357
+ console.log(JSON.stringify(projects, null, 2));
1358
+ return;
1359
+ }
1360
+ if (projects.length === 0) {
1361
+ console.log("No matching projects.");
1362
+ return;
1363
+ }
1364
+ for (const line of formatProjectRows(projects)) console.log(line);
1365
+ }
1366
+ function formatProjectRows(projects) {
1367
+ const width = Math.max(...projects.map((p) => p.key.length));
1368
+ return projects.map((p) => `${p.key.padEnd(width)} ${p.name}`);
1369
+ }
1323
1370
  async function jiraCopy(arg, options) {
1324
1371
  const auth = await requireAuth();
1325
1372
  const key = await resolveKey(arg);
@@ -1470,6 +1517,7 @@ auth.command("login").description("Store site, email, and API token").action(run
1470
1517
  auth.command("logout").description("Remove stored credentials").action(run(logout));
1471
1518
  auth.command("status").description("Show the current login").action(run(status));
1472
1519
  const jira = program.command("jira").description("Jira commands");
1520
+ jira.command("projects [query]").description("List projects (optionally filtered by key or name)").option("--json", "output results as JSON").action(run(jiraProjects));
1473
1521
  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));
1474
1522
  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));
1475
1523
  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));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlass",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "CLI to copy Jira issues and Confluence pages to Markdown, and update Confluence pages.",
5
5
  "license": "MIT",
6
6
  "repository": {