1.0.1: check both tea-cli and tea for every tea command, with a test for each case

This commit is contained in:
Agent committed 2026-10-12 15:45:24 +00:00
1 parent e303c51b23
commit 25cea75eb5
6 files changed
+93 -19

No files matched your search

+25 -13
View File
@@ -5,17 +5,35 @@ import { randomUUID } from 'node:crypto'
import { ForgeError, listAccounts, publicAccount, resetAccountCache, run } from './forge.mjs'
const TEA_BINARIES = ['tea-cli', 'tea']
let teaBinary
const TEA_MISSING = 'neither tea-cli nor tea is installed on the host'
let teaBinary // the binary that worked last (tried first). A miss is never cached, so installing one later just works.
async function which(cmd) {
const r = await run(cmd, ['--version'], { timeoutMs: 10000 })
return r.code === 0 ? (r.stdout || r.stderr).replace(/\x1b\[[0-9;]*m/g, "").trim().split("\n")[0] : null
}
/** Candidate order for tea commands: the last working binary first, then the rest of tea-cli / tea. */
const teaOrder = () => (teaBinary ? [teaBinary, ...TEA_BINARIES.filter((b) => b !== teaBinary)] : TEA_BINARIES)
/** First of tea-cli / tea that runs on this host, or null. */
async function teaBin() {
if (teaBinary !== undefined) return teaBinary
for (const b of TEA_BINARIES) if (await which(b)) return (teaBinary = b)
return (teaBinary = null)
for (const b of teaOrder()) if (await which(b)) return (teaBinary = b)
return null
}
/**
* Run a tea command with whichever of tea-cli / tea exists: both names are tried, and a binary that is not
* there (ENOENT) is skipped. Resolves like `run`, plus the `bin` that ran it.
*/
async function runTea(args, opts) {
for (const bin of teaOrder()) {
const r = await run(bin, args, opts)
if (r.error?.code === 'ENOENT') continue
teaBinary = bin
return { ...r, bin }
}
throw new ForgeError(TEA_MISSING, 400)
}
export async function cliStatus() {
@@ -34,8 +52,6 @@ const cleanHost = (h) => String(h || 'github.com').trim().replace(/^https?:\/\//
/** Add a Gitea/Forgejo login with a token (or user+password, from which tea creates a token). */
export async function giteaLogin({ name, url, token, user, password, insecure }) {
const bin = await teaBin()
if (!bin) throw new ForgeError('tea-cli is not installed on the host', 400)
if (!url) throw new ForgeError('server URL is required', 400)
if (!token && !(user && password)) throw new ForgeError('an access token (or user and password) is required', 400)
let u
@@ -46,8 +62,8 @@ export async function giteaLogin({ name, url, token, user, password, insecure })
if (token) env.GITEA_SERVER_TOKEN = token
else { env.GITEA_SERVER_USER = user; env.GITEA_SERVER_PASSWORD = password }
if (insecure) args.push('--insecure')
const r = await run(bin, args, { env, timeoutMs: 60000 })
if (r.code !== 0) throw fail(r, 'tea-cli logins add')
const r = await runTea(args, { env, timeoutMs: 60000 })
if (r.code !== 0) throw fail(r, `${r.bin} logins add`)
done()
return { ok: true, message: (r.stdout || '').trim() }
}
@@ -132,11 +148,7 @@ export async function logout(accountId) {
if (!a) throw new ForgeError(`unknown account ${accountId}`, 404)
let r
if (a.kind === 'github') r = await run('gh', ['auth', 'logout', '--hostname', a.host, '--user', a.user], { timeoutMs: 30000 })
else {
const bin = await teaBin()
if (!bin) throw new ForgeError('tea-cli is not installed on the host', 400)
r = await run(bin, ['logins', 'delete', a.name], { timeoutMs: 30000 })
}
else r = await runTea(['logins', 'delete', a.name], { timeoutMs: 30000 })
if (r.code !== 0) throw fail(r, 'logout')
done()
return { ok: true }