@kaynooo/forgejo-tools 0.8.0 → 0.9.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 +62 -7
- 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 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.
|
|
36
|
+
`ft <command> --help` lists each command's options. Waiting commands show one line per run with how long it has been waiting or running (`● running for 1m 12s`), redrawn in place on a terminal and left at its last status with the time it took (`✓ success in 3m 2s`), 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
|
@@ -599,15 +599,15 @@ function wholeNumber(value, name, fallback, min = 1) {
|
|
|
599
599
|
badUsage(`${name.startsWith("<") ? name : `--${name}`} takes a whole number from ${min}, not ${value}`);
|
|
600
600
|
return number;
|
|
601
601
|
}
|
|
602
|
-
var DURATION = /^(\d+(?:\.\d+)?)(ms|[
|
|
603
|
-
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000 };
|
|
602
|
+
var DURATION = /^(\d+(?:\.\d+)?)(ms|[smhd])?$/;
|
|
603
|
+
var UNIT_MS = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
604
604
|
function duration(value, name, fallback, unit = "s") {
|
|
605
605
|
if (value === undefined)
|
|
606
606
|
return fallback;
|
|
607
607
|
const match = DURATION.exec(value.trim());
|
|
608
608
|
const ms = match === null ? 0 : Number(match[1]) * UNIT_MS[match[2] ?? unit];
|
|
609
609
|
if (ms < 1)
|
|
610
|
-
badUsage(`--${name} takes a duration such as 30s, 5m or
|
|
610
|
+
badUsage(`--${name} takes a duration such as 30s, 5m, 1h or 7d, not ${value}`);
|
|
611
611
|
return ms;
|
|
612
612
|
}
|
|
613
613
|
async function readInput(path, { noStdin = "nothing is piped on stdin" } = {}) {
|
|
@@ -754,6 +754,7 @@ var LINE_UP = "\x1B[1A\x1B[2K";
|
|
|
754
754
|
var ESC = String.fromCharCode(27);
|
|
755
755
|
var ESCAPE_CODE = new RegExp(`${ESC}(?:\\[[\\x30-\\x3F]*[\\x20-\\x2F]*[\\x40-\\x7E]|\\][^\\u0007${ESC}]*(?:\\u0007|${ESC}\\\\))`, "y");
|
|
756
756
|
var RESET_STYLE = `${ESC}[0m`;
|
|
757
|
+
var CTRL_C = 3;
|
|
757
758
|
var CLOSE_LINK = `${ESC}]8;;${ESC}\\`;
|
|
758
759
|
var CONSOLE_METHODS = ["log", "info", "warn", "error", "debug", "dir", "dirxml", "table", "trace", "group", "groupCollapsed", "count", "timeEnd", "timeLog", "assert"];
|
|
759
760
|
function liveRegion(render, stream = process6.stderr, env = process6.env) {
|
|
@@ -786,12 +787,17 @@ function liveRegion(render, stream = process6.stderr, env = process6.env) {
|
|
|
786
787
|
};
|
|
787
788
|
const timer = setInterval(draw, FRAME_MS);
|
|
788
789
|
const restore = interceptOutput(clear);
|
|
789
|
-
const
|
|
790
|
+
const release = swallowInput();
|
|
791
|
+
const onExit = () => {
|
|
792
|
+
clear();
|
|
793
|
+
release();
|
|
794
|
+
};
|
|
790
795
|
const signals = holdSignals(stop);
|
|
791
796
|
function stop() {
|
|
792
797
|
clearInterval(timer);
|
|
793
798
|
clear();
|
|
794
799
|
restore();
|
|
800
|
+
release();
|
|
795
801
|
process6.off("exit", onExit);
|
|
796
802
|
signals.release();
|
|
797
803
|
}
|
|
@@ -834,6 +840,29 @@ function cutToWidth(line, width) {
|
|
|
834
840
|
}
|
|
835
841
|
return `${kept}${RESET_STYLE}${linked ? CLOSE_LINK : ""}`;
|
|
836
842
|
}
|
|
843
|
+
function swallowInput() {
|
|
844
|
+
const input = process6.stdin;
|
|
845
|
+
if (!input.isTTY)
|
|
846
|
+
return () => {
|
|
847
|
+
return;
|
|
848
|
+
};
|
|
849
|
+
const onData = (data) => {
|
|
850
|
+
if (data.includes(CTRL_C))
|
|
851
|
+
process6.kill(process6.pid, "SIGINT");
|
|
852
|
+
};
|
|
853
|
+
input.setRawMode(true);
|
|
854
|
+
input.on("data", onData);
|
|
855
|
+
input.resume();
|
|
856
|
+
let released = false;
|
|
857
|
+
return () => {
|
|
858
|
+
if (released)
|
|
859
|
+
return;
|
|
860
|
+
released = true;
|
|
861
|
+
input.off("data", onData);
|
|
862
|
+
input.pause();
|
|
863
|
+
input.setRawMode(false);
|
|
864
|
+
};
|
|
865
|
+
}
|
|
837
866
|
function interceptOutput(clear) {
|
|
838
867
|
const targets = [...CONSOLE_METHODS.map((name) => [console, name]), [process6.stdout, "write"], [process6.stderr, "write"]];
|
|
839
868
|
const wrapped = targets.map(([target, name]) => {
|
|
@@ -1292,7 +1321,7 @@ async function currentSession(sessions) {
|
|
|
1292
1321
|
return;
|
|
1293
1322
|
}
|
|
1294
1323
|
// package.json
|
|
1295
|
-
var version = "0.
|
|
1324
|
+
var version = "0.9.0";
|
|
1296
1325
|
|
|
1297
1326
|
// src/args.ts
|
|
1298
1327
|
var repoOption = {
|
|
@@ -1980,8 +2009,12 @@ var EPOCH = Date.parse("2000-01-01T00:00:00Z");
|
|
|
1980
2009
|
function runSeconds(run) {
|
|
1981
2010
|
return run.duration / NANOSECONDS;
|
|
1982
2011
|
}
|
|
2012
|
+
function startedAt(run) {
|
|
2013
|
+
const at = Date.parse(run.started);
|
|
2014
|
+
return Number.isNaN(at) || at < EPOCH ? undefined : at;
|
|
2015
|
+
}
|
|
1983
2016
|
function startedAge(run, now = Date.now()) {
|
|
1984
|
-
return
|
|
2017
|
+
return startedAt(run) === undefined ? "-" : formatAge(run.started, now);
|
|
1985
2018
|
}
|
|
1986
2019
|
var RUNS_PAGE = 50;
|
|
1987
2020
|
async function runsListing(limit, repo, workflow) {
|
|
@@ -3068,7 +3101,8 @@ var STALLED2 = 3 * 60000;
|
|
|
3068
3101
|
async function watchRuns(pick, timeout, repo, idle, out = process.stdout) {
|
|
3069
3102
|
const started = Date.now();
|
|
3070
3103
|
const latest = new Map;
|
|
3071
|
-
const
|
|
3104
|
+
const seen = new Map;
|
|
3105
|
+
const line = (run) => `${run.workflow_id} run ${run.id} on ${run.prettyref}: ${badge(run.status, out)}${elapsed(run, seen.get(run.id) ?? Date.now())}`;
|
|
3072
3106
|
const region = takesControlCodes(out) ? liveRegion(() => [...latest.values()].map(line), out) : undefined;
|
|
3073
3107
|
const followed = new Set;
|
|
3074
3108
|
const failed = new Set;
|
|
@@ -3080,6 +3114,8 @@ async function watchRuns(pick, timeout, repo, idle, out = process.stdout) {
|
|
|
3080
3114
|
for (const run of runs)
|
|
3081
3115
|
followed.add(run.id);
|
|
3082
3116
|
for (const run of runs) {
|
|
3117
|
+
if (!seen.has(run.id))
|
|
3118
|
+
seen.set(run.id, Date.now());
|
|
3083
3119
|
if (region === undefined && latest.get(run.id)?.status !== run.status)
|
|
3084
3120
|
out.write(`${line(run)}
|
|
3085
3121
|
`);
|
|
@@ -3143,6 +3179,12 @@ ${run.html_url}`);
|
|
|
3143
3179
|
}
|
|
3144
3180
|
}
|
|
3145
3181
|
}
|
|
3182
|
+
function elapsed(run, seen) {
|
|
3183
|
+
if (FINISHED.has(run.status))
|
|
3184
|
+
return run.duration > 0 ? ` in ${formatSeconds(runSeconds(run))}` : "";
|
|
3185
|
+
const seconds = Math.floor((Date.now() - (startedAt(run) ?? seen)) / 1000);
|
|
3186
|
+
return seconds > 0 ? ` for ${formatSeconds(seconds)}` : "";
|
|
3187
|
+
}
|
|
3146
3188
|
async function watch(args) {
|
|
3147
3189
|
const parsed = parse(args, usage4, options5);
|
|
3148
3190
|
const { values } = parsed;
|
|
@@ -3186,6 +3228,7 @@ var REGISTRY_POLL = 5000;
|
|
|
3186
3228
|
var limits = { pushMs: 5 * 60000 };
|
|
3187
3229
|
var REGISTRY_INFO_MS = 30000;
|
|
3188
3230
|
var NO_RUN2 = 3 * 60000;
|
|
3231
|
+
var RUNS_LOOKED_AT = 20;
|
|
3189
3232
|
function taggedVersion(tag, prefix) {
|
|
3190
3233
|
const version = tag.slice(prefix.length);
|
|
3191
3234
|
return tag.startsWith(prefix) && VERSION.test(version) ? version : undefined;
|
|
@@ -3250,6 +3293,14 @@ async function waitForRegistry(name, version) {
|
|
|
3250
3293
|
}
|
|
3251
3294
|
return false;
|
|
3252
3295
|
}
|
|
3296
|
+
async function alreadyPassed(sha) {
|
|
3297
|
+
try {
|
|
3298
|
+
const own = newestPerWorkflow((await recentRuns(RUNS_LOOKED_AT)).filter((run) => run.commit_sha === sha));
|
|
3299
|
+
return own.length > 0 && own.every((run) => FINISHED.has(run.status)) && ["success", "skipped"].includes(overall(own));
|
|
3300
|
+
} catch {
|
|
3301
|
+
return false;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3253
3304
|
async function testsPass(sha, branch, timeout) {
|
|
3254
3305
|
if (!await gitOk(["merge-base", "--is-ancestor", sha, `origin/${branch}`])) {
|
|
3255
3306
|
const held = holdSignals();
|
|
@@ -3265,6 +3316,10 @@ async function testsPass(sha, branch, timeout) {
|
|
|
3265
3316
|
fail(`origin refused the push of ${sha.slice(0, 7)}, so nothing was bumped:
|
|
3266
3317
|
${push.stderr.trim()}`, 1);
|
|
3267
3318
|
console.log(`pushed ${branch} at ${sha.slice(0, 7)}, waiting for its runs before the bump`);
|
|
3319
|
+
} else if (await alreadyPassed(sha)) {
|
|
3320
|
+
return true;
|
|
3321
|
+
} else {
|
|
3322
|
+
console.log(`${branch} at ${sha.slice(0, 7)} is on origin, waiting for its runs before the bump`);
|
|
3268
3323
|
}
|
|
3269
3324
|
return await watchRuns((runs) => newestPerWorkflow(runs.filter((run) => run.commit_sha === sha)), timeout, undefined, NO_RUN2) === 0;
|
|
3270
3325
|
}
|
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.9.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",
|