terminfo.dev 1.4.0 → 1.6.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/package.json +1 -1
- package/src/detect.ts +20 -6
- package/src/index.ts +31 -17
- package/src/submit.ts +15 -54
- package/src/tty.ts +22 -0
package/package.json
CHANGED
package/src/detect.ts
CHANGED
|
@@ -52,15 +52,19 @@ export function detectTerminal(): TerminalInfo {
|
|
|
52
52
|
let name = "unknown"
|
|
53
53
|
let version = ""
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
break
|
|
55
|
+
// On macOS, __CFBundleIdentifier is the most reliable — set by the actual running app
|
|
56
|
+
const bundleId = process.env.__CFBundleIdentifier
|
|
57
|
+
if (bundleId && os === "macos") {
|
|
58
|
+
for (const [termName, bid] of Object.entries(BUNDLE_IDS)) {
|
|
59
|
+
if (bundleId === bid) { name = termName; break }
|
|
60
|
+
}
|
|
61
|
+
// If bundle ID didn't match known terminals, use it as-is
|
|
62
|
+
if (name === "unknown" && bundleId) {
|
|
63
|
+
name = bundleId.split(".").pop() ?? bundleId
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
// Check $TERM_PROGRAM
|
|
67
|
+
// Check $TERM_PROGRAM (more reliable than env var detectors for cross-app scenarios)
|
|
64
68
|
if (name === "unknown") {
|
|
65
69
|
const termProgram = process.env.TERM_PROGRAM
|
|
66
70
|
if (termProgram) {
|
|
@@ -69,6 +73,16 @@ export function detectTerminal(): TerminalInfo {
|
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
// Check specific env vars (may be inherited from parent, so lower priority)
|
|
77
|
+
if (name === "unknown") {
|
|
78
|
+
for (const { env, name: n } of ENV_DETECTORS) {
|
|
79
|
+
if (process.env[env]) {
|
|
80
|
+
name = n
|
|
81
|
+
break
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
// Check $TERMINAL_EMULATOR (Linux)
|
|
73
87
|
if (name === "unknown") {
|
|
74
88
|
const termEmu = process.env.TERMINAL_EMULATOR
|
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { dirname, join } from "node:path"
|
|
|
18
18
|
import { fileURLToPath } from "node:url"
|
|
19
19
|
import { detectTerminal } from "./detect.ts"
|
|
20
20
|
import { ALL_PROBES } from "./probes/index.ts"
|
|
21
|
-
import { withRawMode } from "./tty.ts"
|
|
21
|
+
import { withRawMode, drainStdin } from "./tty.ts"
|
|
22
22
|
import { submitResults } from "./submit.ts"
|
|
23
23
|
|
|
24
24
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
@@ -76,21 +76,11 @@ async function runProbes(): Promise<ProbeResults> {
|
|
|
76
76
|
notes[probe.id] = `error: ${err instanceof Error ? err.message : String(err)}`
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
// Drain all pending responses before exiting alt screen (still in raw mode)
|
|
80
|
+
await drainStdin(500)
|
|
79
81
|
process.stdout.write("\x1b[?1049l")
|
|
80
82
|
})
|
|
81
83
|
|
|
82
|
-
// Drain any late-arriving escape sequence responses before output
|
|
83
|
-
await new Promise<void>((resolve) => {
|
|
84
|
-
process.stdin.resume()
|
|
85
|
-
const timer = setTimeout(() => {
|
|
86
|
-
process.stdin.pause()
|
|
87
|
-
process.stdin.removeAllListeners("readable")
|
|
88
|
-
resolve()
|
|
89
|
-
}, 100)
|
|
90
|
-
process.stdin.on("readable", () => { while (process.stdin.read() !== null) {} })
|
|
91
|
-
timer.unref()
|
|
92
|
-
})
|
|
93
|
-
|
|
94
84
|
return { terminal, results, notes, responses, passed, total: ALL_PROBES.length }
|
|
95
85
|
}
|
|
96
86
|
|
|
@@ -183,21 +173,45 @@ program
|
|
|
183
173
|
program
|
|
184
174
|
.command("submit")
|
|
185
175
|
.description("Run all probes and submit results to terminfo.dev via GitHub issue")
|
|
186
|
-
.
|
|
176
|
+
.option("--terminal-name <name>", "Override detected terminal name")
|
|
177
|
+
.option("--terminal-version <version>", "Override detected terminal version")
|
|
178
|
+
.action(async (opts) => {
|
|
179
|
+
// Confirm details BEFORE probes (stdin is still clean)
|
|
180
|
+
const terminal = detectTerminal()
|
|
181
|
+
const name = opts.terminalName ?? terminal.name
|
|
182
|
+
const version = opts.terminalVersion ?? terminal.version
|
|
183
|
+
|
|
184
|
+
printHeader(terminal)
|
|
185
|
+
console.log(``)
|
|
186
|
+
console.log(` Will submit results for \x1b[1m${name}${version ? ` ${version}` : ""}\x1b[0m on ${terminal.os}`)
|
|
187
|
+
if (!version) {
|
|
188
|
+
console.log(` \x1b[33m⚠ No version detected. Use --terminal-version to specify.\x1b[0m`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { createInterface } = await import("node:readline")
|
|
192
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
193
|
+
const proceed = await new Promise<string>((resolve) => {
|
|
194
|
+
rl.question(`\n Press Enter to run probes and submit (or Ctrl+C to cancel) `, (answer) => {
|
|
195
|
+
rl.close()
|
|
196
|
+
resolve(answer)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Now run probes (stdin goes to raw mode, no conflict)
|
|
187
201
|
const data = await runProbes()
|
|
188
202
|
printResults(data)
|
|
189
203
|
|
|
190
204
|
console.log(`\nSubmitting results to terminfo.dev...`)
|
|
191
205
|
const url = await submitResults({
|
|
192
|
-
terminal:
|
|
193
|
-
terminalVersion:
|
|
206
|
+
terminal: name,
|
|
207
|
+
terminalVersion: version,
|
|
194
208
|
os: data.terminal.os,
|
|
195
209
|
osVersion: data.terminal.osVersion,
|
|
196
210
|
results: data.results,
|
|
197
211
|
notes: data.notes,
|
|
198
212
|
responses: data.responses,
|
|
199
213
|
generated: new Date().toISOString(),
|
|
200
|
-
cliVersion: "1.
|
|
214
|
+
cliVersion: "1.6.0",
|
|
201
215
|
probeCount: ALL_PROBES.length,
|
|
202
216
|
})
|
|
203
217
|
if (url) {
|
package/src/submit.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { writeFileSync, unlinkSync } from "node:fs"
|
|
|
6
6
|
import { tmpdir } from "node:os"
|
|
7
7
|
import { join } from "node:path"
|
|
8
8
|
import { execFileSync } from "node:child_process"
|
|
9
|
-
import { createInterface } from "node:readline"
|
|
10
9
|
|
|
11
10
|
const REPO = "beorn/terminfo.dev"
|
|
12
11
|
|
|
@@ -23,65 +22,28 @@ interface SubmitData {
|
|
|
23
22
|
probeCount?: number
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
/** Drain any leftover bytes from stdin (e.g., late-arriving escape sequence responses) */
|
|
27
|
-
async function drainStdin(): Promise<void> {
|
|
28
|
-
return new Promise((resolve) => {
|
|
29
|
-
if (!process.stdin.readable) { resolve(); return }
|
|
30
|
-
process.stdin.resume()
|
|
31
|
-
const timer = setTimeout(() => {
|
|
32
|
-
process.stdin.pause()
|
|
33
|
-
process.stdin.removeAllListeners("readable")
|
|
34
|
-
resolve()
|
|
35
|
-
}, 200)
|
|
36
|
-
process.stdin.on("readable", () => {
|
|
37
|
-
while (process.stdin.read() !== null) {} // discard
|
|
38
|
-
})
|
|
39
|
-
timer.unref()
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function prompt(question: string, defaultValue?: string): Promise<string> {
|
|
44
|
-
await drainStdin()
|
|
45
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
46
|
-
const suffix = defaultValue ? ` [${defaultValue}]` : ""
|
|
47
|
-
return new Promise((resolve) => {
|
|
48
|
-
rl.question(`${question}${suffix}: `, (answer) => {
|
|
49
|
-
rl.close()
|
|
50
|
-
resolve(answer.trim() || defaultValue || "")
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
25
|
export async function submitResults(data: SubmitData): Promise<string | null> {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
data.terminalVersion
|
|
60
|
-
|
|
26
|
+
const passed = Object.values(data.results).filter(Boolean).length
|
|
27
|
+
const total = Object.keys(data.results).length
|
|
28
|
+
const pct = Math.round((passed / total) * 100)
|
|
29
|
+
const ver = data.terminalVersion ? ` ${data.terminalVersion}` : ""
|
|
30
|
+
|
|
31
|
+
console.log(`\n Submitting: \x1b[1m${data.terminal}${ver}\x1b[0m on ${data.os} — ${pct}% (${passed}/${total})`)
|
|
61
32
|
|
|
62
33
|
if (!data.terminalVersion) {
|
|
63
|
-
console.log(
|
|
34
|
+
console.log(` \x1b[33m⚠ No version detected — use --terminal-version to specify\x1b[0m`)
|
|
64
35
|
}
|
|
65
36
|
|
|
66
37
|
// Check for duplicates
|
|
67
38
|
if (hasGhCli()) {
|
|
68
39
|
const existing = checkDuplicate(data.terminal, data.terminalVersion, data.os)
|
|
69
40
|
if (existing) {
|
|
70
|
-
console.log(
|
|
71
|
-
console.log(`
|
|
72
|
-
const proceed = await prompt(" Submit anyway? (y/N)", "N")
|
|
73
|
-
if (proceed.toLowerCase() !== "y") {
|
|
74
|
-
console.log(`Skipped.`)
|
|
75
|
-
return null
|
|
76
|
-
}
|
|
41
|
+
console.log(` \x1b[33m⚠ Similar submission exists: ${existing}\x1b[0m`)
|
|
42
|
+
console.log(` Submitting anyway (different probe version may have new results)`)
|
|
77
43
|
}
|
|
78
44
|
}
|
|
79
45
|
|
|
80
|
-
const
|
|
81
|
-
const total = Object.keys(data.results).length
|
|
82
|
-
const pct = Math.round((passed / total) * 100)
|
|
83
|
-
|
|
84
|
-
const title = `[census] ${data.terminal}${data.terminalVersion ? ` ${data.terminalVersion}` : ""} on ${data.os} — ${pct}% (${passed}/${total})`
|
|
46
|
+
const title = `[census] ${data.terminal}${ver} on ${data.os} — ${pct}% (${passed}/${total})`
|
|
85
47
|
|
|
86
48
|
const body = `## Community Census Result
|
|
87
49
|
|
|
@@ -114,8 +76,8 @@ ${JSON.stringify(data, null, 2)}
|
|
|
114
76
|
if (!hasGhCli()) {
|
|
115
77
|
const filename = `terminfo-${data.terminal}-${data.os}-${Date.now()}.json`
|
|
116
78
|
writeFileSync(filename, JSON.stringify(data, null, 2))
|
|
117
|
-
console.log(`\n\x1b[33mgh CLI not found. Results saved to ${filename}\x1b[0m`)
|
|
118
|
-
console.log(`To submit: https://github.com/${REPO}/issues/new`)
|
|
79
|
+
console.log(`\n \x1b[33mgh CLI not found. Results saved to ${filename}\x1b[0m`)
|
|
80
|
+
console.log(` To submit: https://github.com/${REPO}/issues/new`)
|
|
119
81
|
return null
|
|
120
82
|
}
|
|
121
83
|
|
|
@@ -130,8 +92,8 @@ ${JSON.stringify(data, null, 2)}
|
|
|
130
92
|
], { encoding: "utf-8", timeout: 30000 })
|
|
131
93
|
return result.trim()
|
|
132
94
|
} catch (err) {
|
|
133
|
-
console.error(
|
|
134
|
-
console.error(err instanceof Error ? err.message : String(err))
|
|
95
|
+
console.error(` \x1b[31mFailed to create issue\x1b[0m`)
|
|
96
|
+
console.error(` ${err instanceof Error ? err.message : String(err)}`)
|
|
135
97
|
return null
|
|
136
98
|
} finally {
|
|
137
99
|
try { unlinkSync(bodyFile) } catch {}
|
|
@@ -159,8 +121,7 @@ function checkDuplicate(terminal: string, version: string, os: string): string |
|
|
|
159
121
|
"--json", "url,title",
|
|
160
122
|
"--jq", ".[0] | .title + \" \" + .url",
|
|
161
123
|
], { encoding: "utf-8", timeout: 10000 })
|
|
162
|
-
|
|
163
|
-
return trimmed || null
|
|
124
|
+
return result.trim() || null
|
|
164
125
|
} catch {
|
|
165
126
|
return null
|
|
166
127
|
}
|
package/src/tty.ts
CHANGED
|
@@ -89,6 +89,28 @@ export async function queryMode(modeNumber: number): Promise<"set" | "reset" | "
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Drain all pending bytes from stdin (late-arriving escape sequence responses).
|
|
94
|
+
* Waits up to `ms` milliseconds for bytes to stop arriving.
|
|
95
|
+
*/
|
|
96
|
+
export async function drainStdin(ms = 300): Promise<void> {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
if (!process.stdin.readable) { resolve(); return }
|
|
99
|
+
process.stdin.resume()
|
|
100
|
+
let timer = setTimeout(done, ms)
|
|
101
|
+
function onData() {
|
|
102
|
+
while (process.stdin.read() !== null) {} // discard
|
|
103
|
+
clearTimeout(timer)
|
|
104
|
+
timer = setTimeout(done, ms) // reset timer on each new data
|
|
105
|
+
}
|
|
106
|
+
function done() {
|
|
107
|
+
process.stdin.removeListener("readable", onData)
|
|
108
|
+
resolve()
|
|
109
|
+
}
|
|
110
|
+
process.stdin.on("readable", onData)
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
/**
|
|
93
115
|
* Run a function with stdin in raw mode.
|
|
94
116
|
* Restores original mode on exit.
|