@kaynooo/forgejo-tools 0.7.0 → 0.8.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 +1 -1
  2. package/dist/cli.js +106 -91
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -33,7 +33,7 @@ Needs [tea](https://gitea.com/gitea/tea) logged in to the Forgejo instance (`tea
33
33
  | `ft milestone <title\|id>` | shows a milestone's open issues as `ft board` does (available, taken, claimed by one gone); `ft milestone new <title> [-d text] [--due YYYY-MM-DD]` creates one through the API, sending no `due_on` unless `--due` is given, never a zero date; `ft milestone close <m>` refuses while issues are open in it, naming them, unless `--force` |
34
34
  | `ft remote [--fix]` | switches an origin using an ssh alias (`forgejo:owner/repo`) to `git@<host>:owner/repo.git`, which tea matches; the host comes from `--host` or `remoteHost` in `~/.config/ft/config.json` (`{"remoteHost": "git.example.com"}`), and with neither it checks nothing |
35
35
 
36
- `ft <command> --help` lists each command's options. Waiting commands exit 0 when the runs pass, 1 when one fails, 2 on a timeout, a stalled queue or bad usage; the other commands exit 0 when done, 1 when Forgejo refuses or a check fails, 2 on a usage error.
36
+ `ft <command> --help` lists each command's options. Waiting commands show one line per run, redrawn in place on a terminal and left at its last status, and a line per status change in a log or a pipe; they exit 0 when the runs pass, 1 when one fails, 2 on a timeout, a stalled queue or bad usage; the other commands exit 0 when done, 1 when Forgejo refuses or a check fails, 2 on a usage error.
37
37
 
38
38
  ## CI
39
39
 
package/dist/cli.js CHANGED
@@ -1222,8 +1222,77 @@ function capped(rows, limit) {
1222
1222
  var DAY_MS = 24 * 60 * 60 * 1000;
1223
1223
  // ../kit/src/secrets.ts
1224
1224
  var SECRET_WORDS = new Set(["key", "pass", "pwd", "auth", "salt", "cookie", "session", "jwt", "ssn", "iban", "card", "cvv", "otp"]);
1225
+ // ../kit/src/sessions.ts
1226
+ import { homedir } from "os";
1227
+ import { join } from "path";
1228
+ import process10 from "process";
1229
+ var {Glob } = globalThis.Bun;
1230
+ var SESSION_FILES = new Glob("*.json");
1231
+ var ACCOUNT_DIRS = new Glob("*/sessions");
1232
+ async function sessionDirs() {
1233
+ const home = homedir();
1234
+ const dirs = new Set([join(home, ".claude", "sessions")]);
1235
+ if (process10.env.CLAUDE_CONFIG_DIR !== undefined)
1236
+ dirs.add(join(process10.env.CLAUDE_CONFIG_DIR, "sessions"));
1237
+ try {
1238
+ for await (const dir of ACCOUNT_DIRS.scan({ cwd: join(home, ".claude-accounts"), onlyFiles: false, absolute: true }))
1239
+ dirs.add(dir);
1240
+ } catch {}
1241
+ return [...dirs];
1242
+ }
1243
+ async function procStat(pid) {
1244
+ try {
1245
+ const stat = await Bun.file(`/proc/${pid}/stat`).text();
1246
+ return stat.slice(stat.lastIndexOf(")") + 2).split(" ");
1247
+ } catch {
1248
+ return;
1249
+ }
1250
+ }
1251
+ async function isAlive(pid, procStart) {
1252
+ const stat = await procStat(pid);
1253
+ if (stat === undefined)
1254
+ return false;
1255
+ return procStart === undefined || stat[19] === procStart;
1256
+ }
1257
+ async function liveSessions() {
1258
+ const sessions = new Map;
1259
+ for (const dir of await sessionDirs()) {
1260
+ let files;
1261
+ try {
1262
+ files = await Array.fromAsync(SESSION_FILES.scan({ cwd: dir, absolute: true }));
1263
+ } catch {
1264
+ continue;
1265
+ }
1266
+ for (const file of files) {
1267
+ let entry;
1268
+ try {
1269
+ entry = await Bun.file(file).json();
1270
+ } catch {
1271
+ continue;
1272
+ }
1273
+ if (entry.name === undefined || entry.pid === undefined || sessions.has(entry.pid) || !await isAlive(entry.pid, entry.procStart))
1274
+ continue;
1275
+ sessions.set(entry.pid, { name: entry.name, pid: entry.pid, status: entry.status ?? "unknown", cwd: entry.cwd ?? "" });
1276
+ }
1277
+ }
1278
+ return [...sessions.values()];
1279
+ }
1280
+ async function currentSession(sessions) {
1281
+ const byPid = new Map(sessions.map((session) => [session.pid, session]));
1282
+ let pid = process10.ppid;
1283
+ for (let depth = 0;depth < 32 && pid > 1; depth++) {
1284
+ const session = byPid.get(pid);
1285
+ if (session !== undefined)
1286
+ return session;
1287
+ const parent = Number((await procStat(pid))?.[1]);
1288
+ if (!Number.isInteger(parent))
1289
+ return;
1290
+ pid = parent;
1291
+ }
1292
+ return;
1293
+ }
1225
1294
  // package.json
1226
- var version = "0.7.0";
1295
+ var version = "0.8.0";
1227
1296
 
1228
1297
  // src/args.ts
1229
1298
  var repoOption = {
@@ -1625,76 +1694,6 @@ async function closeMilestone(id, repo) {
1625
1694
  await repoWrite("PATCH", `milestones/${id}`, { state: "closed" }, repo);
1626
1695
  }
1627
1696
 
1628
- // src/sessions.ts
1629
- import { homedir } from "os";
1630
- import { join } from "path";
1631
- import process11 from "process";
1632
- var {Glob } = globalThis.Bun;
1633
- var SESSION_FILES = new Glob("*.json");
1634
- var ACCOUNT_DIRS = new Glob("*/sessions");
1635
- async function sessionDirs() {
1636
- const home = homedir();
1637
- const dirs = new Set([join(home, ".claude", "sessions")]);
1638
- if (process11.env.CLAUDE_CONFIG_DIR !== undefined)
1639
- dirs.add(join(process11.env.CLAUDE_CONFIG_DIR, "sessions"));
1640
- try {
1641
- for await (const dir of ACCOUNT_DIRS.scan({ cwd: join(home, ".claude-accounts"), onlyFiles: false, absolute: true }))
1642
- dirs.add(dir);
1643
- } catch {}
1644
- return [...dirs];
1645
- }
1646
- async function procStat(pid) {
1647
- try {
1648
- const stat = await Bun.file(`/proc/${pid}/stat`).text();
1649
- return stat.slice(stat.lastIndexOf(")") + 2).split(" ");
1650
- } catch {
1651
- return;
1652
- }
1653
- }
1654
- async function isAlive(pid, procStart) {
1655
- const stat = await procStat(pid);
1656
- if (stat === undefined)
1657
- return false;
1658
- return procStart === undefined || stat[19] === procStart;
1659
- }
1660
- async function liveSessions() {
1661
- const sessions = new Map;
1662
- for (const dir of await sessionDirs()) {
1663
- let files;
1664
- try {
1665
- files = await Array.fromAsync(SESSION_FILES.scan({ cwd: dir, absolute: true }));
1666
- } catch {
1667
- continue;
1668
- }
1669
- for (const file of files) {
1670
- let entry;
1671
- try {
1672
- entry = await Bun.file(file).json();
1673
- } catch {
1674
- continue;
1675
- }
1676
- if (entry.name === undefined || entry.pid === undefined || sessions.has(entry.pid) || !await isAlive(entry.pid, entry.procStart))
1677
- continue;
1678
- sessions.set(entry.pid, { name: entry.name, pid: entry.pid, status: entry.status ?? "unknown", cwd: entry.cwd ?? "" });
1679
- }
1680
- }
1681
- return [...sessions.values()];
1682
- }
1683
- async function currentSession(sessions) {
1684
- const byPid = new Map(sessions.map((session) => [session.pid, session]));
1685
- let pid = process11.ppid;
1686
- for (let depth = 0;depth < 32 && pid > 1; depth++) {
1687
- const session = byPid.get(pid);
1688
- if (session !== undefined)
1689
- return session;
1690
- const parent = Number((await procStat(pid))?.[1]);
1691
- if (!Number.isInteger(parent))
1692
- return;
1693
- pid = parent;
1694
- }
1695
- return;
1696
- }
1697
-
1698
1697
  // src/commands/board.ts
1699
1698
  var LINE_START = /^/gm;
1700
1699
  var TITLE_WIDTH = 80;
@@ -3066,9 +3065,11 @@ var options5 = {
3066
3065
  var timing = { poll: 1e4 };
3067
3066
  var LIMIT = 20;
3068
3067
  var STALLED2 = 3 * 60000;
3069
- async function watchRuns(pick, timeout, repo, idle) {
3068
+ async function watchRuns(pick, timeout, repo, idle, out = process.stdout) {
3070
3069
  const started = Date.now();
3071
- const reported = new Map;
3070
+ const latest = new Map;
3071
+ const line = (run) => `${run.workflow_id} run ${run.id} on ${run.prettyref}: ${badge(run.status, out)}`;
3072
+ const region = takesControlCodes(out) ? liveRegion(() => [...latest.values()].map(line), out) : undefined;
3072
3073
  const followed = new Set;
3073
3074
  const failed = new Set;
3074
3075
  let moving = started;
@@ -3079,10 +3080,12 @@ async function watchRuns(pick, timeout, repo, idle) {
3079
3080
  for (const run of runs)
3080
3081
  followed.add(run.id);
3081
3082
  for (const run of runs) {
3082
- if (reported.get(run.id) !== run.status)
3083
- console.log(`${run.workflow_id} run ${run.id} on ${run.prettyref}: ${badge(run.status, process.stdout)}`);
3084
- reported.set(run.id, run.status);
3083
+ if (region === undefined && latest.get(run.id)?.status !== run.status)
3084
+ out.write(`${line(run)}
3085
+ `);
3086
+ latest.set(run.id, run);
3085
3087
  }
3088
+ region?.redraw();
3086
3089
  if (runs.length === 0) {
3087
3090
  if (idle !== undefined && Date.now() - started > idle) {
3088
3091
  console.log("no run started, so nothing was waited for");
@@ -3110,21 +3113,33 @@ ${run.html_url}`);
3110
3113
  fail(`queued runs are not moving: ${stall.reason}`);
3111
3114
  return;
3112
3115
  }
3113
- for (;; ) {
3114
- await Bun.sleep(timing.poll);
3115
- if (Date.now() - started > timeout)
3116
- fail(`the runs did not finish in ${formatDuration(timeout)}`);
3117
- try {
3118
- const code = await poll();
3119
- if (code !== undefined)
3120
- return code;
3121
- } catch (error) {
3122
- if (error instanceof Exit)
3123
- throw error;
3124
- const message = error instanceof Error ? error.message : String(error);
3125
- if (isPermanentFailure(error))
3126
- fail(message);
3127
- console.warn(`ft watch: ${message}, polling again`);
3116
+ async function settle() {
3117
+ for (;; ) {
3118
+ await Bun.sleep(timing.poll);
3119
+ if (Date.now() - started > timeout)
3120
+ fail(`the runs did not finish in ${formatDuration(timeout)}`);
3121
+ try {
3122
+ const code = await poll();
3123
+ if (code !== undefined)
3124
+ return code;
3125
+ } catch (error) {
3126
+ if (error instanceof Exit)
3127
+ throw error;
3128
+ const message = error instanceof Error ? error.message : String(error);
3129
+ if (isPermanentFailure(error))
3130
+ fail(message);
3131
+ console.warn(`ft watch: ${message}, polling again`);
3132
+ }
3133
+ }
3134
+ }
3135
+ try {
3136
+ return await settle();
3137
+ } finally {
3138
+ if (region !== undefined) {
3139
+ region.stop();
3140
+ for (const run of latest.values())
3141
+ out.write(`${line(run)}
3142
+ `);
3128
3143
  }
3129
3144
  }
3130
3145
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaynooo/forgejo-tools",
3
3
  "type": "module",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "description": "ft: Forgejo CI runs, logs, releases and issue checklists from the terminal, through tea.",
6
6
  "author": "Kaynooo <contact@kaynooo.fr>",
7
7
  "license": "MIT",