@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.
- package/README.md +1 -1
- package/dist/cli.js +106 -91
- 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.
|
|
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
|
|
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 (
|
|
3083
|
-
|
|
3084
|
-
|
|
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
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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.
|
|
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",
|