terminfo.dev 1.1.0 → 1.2.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/index.ts +23 -17
- package/src/report.tsx +28 -9
- package/src/submit.ts +56 -2
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -25,10 +25,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
25
25
|
|
|
26
26
|
/** Load feature slugs from features.json for OSC 8 hyperlinks */
|
|
27
27
|
function loadFeatureSlugs(): Record<string, string> {
|
|
28
|
-
const candidates = [
|
|
29
|
-
join(__dirname, "..", "..", "features.json"),
|
|
30
|
-
join(__dirname, "..", "..", "..", "features.json"),
|
|
31
|
-
]
|
|
28
|
+
const candidates = [join(__dirname, "..", "..", "features.json"), join(__dirname, "..", "..", "..", "features.json")]
|
|
32
29
|
for (const path of candidates) {
|
|
33
30
|
try {
|
|
34
31
|
const raw = JSON.parse(readFileSync(path, "utf-8"))
|
|
@@ -90,7 +87,9 @@ function printHeader(terminal: ReturnType<typeof detectTerminal>) {
|
|
|
90
87
|
console.log(`\x1b[1m${siteLink}\x1b[0m — can your terminal do that?\n`)
|
|
91
88
|
console.log(` Terminal: \x1b[1m${terminal.name}\x1b[0m${terminal.version ? ` ${terminal.version}` : ""}`)
|
|
92
89
|
console.log(` Platform: ${terminal.os} ${terminal.osVersion}`)
|
|
93
|
-
console.log(
|
|
90
|
+
console.log(
|
|
91
|
+
` Probes: ${ALL_PROBES.length} features across ${new Set(ALL_PROBES.map((p) => p.id.split(".")[0])).size} categories`,
|
|
92
|
+
)
|
|
94
93
|
console.log(` Website: ${link("https://terminfo.dev", "https://terminfo.dev")}`)
|
|
95
94
|
}
|
|
96
95
|
|
|
@@ -115,7 +114,7 @@ function printResults(data: ProbeResults) {
|
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
for (const [cat, probes] of categories) {
|
|
118
|
-
const catPassed = probes.filter(p => p.pass).length
|
|
117
|
+
const catPassed = probes.filter((p) => p.pass).length
|
|
119
118
|
const color = catPassed === probes.length ? "\x1b[32m" : catPassed > 0 ? "\x1b[33m" : "\x1b[31m"
|
|
120
119
|
const catLink = link(`https://terminfo.dev/${cat}`, cat)
|
|
121
120
|
console.log(`${color}${catLink}\x1b[0m (${catPassed}/${probes.length})`)
|
|
@@ -145,21 +144,28 @@ program
|
|
|
145
144
|
const data = await runProbes()
|
|
146
145
|
|
|
147
146
|
if (opts.json) {
|
|
148
|
-
console.log(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
console.log(
|
|
148
|
+
JSON.stringify(
|
|
149
|
+
{
|
|
150
|
+
terminal: data.terminal.name,
|
|
151
|
+
terminalVersion: data.terminal.version,
|
|
152
|
+
os: data.terminal.os,
|
|
153
|
+
osVersion: data.terminal.osVersion,
|
|
154
|
+
source: "community",
|
|
155
|
+
generated: new Date().toISOString(),
|
|
156
|
+
results: data.results,
|
|
157
|
+
notes: data.notes,
|
|
158
|
+
responses: data.responses,
|
|
159
|
+
},
|
|
160
|
+
null,
|
|
161
|
+
2,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
159
164
|
return
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
printResults(data)
|
|
168
|
+
console.log(`\n\x1b[2mContribute these results: \x1b[0m\x1b[1mnpx terminfo.dev submit\x1b[0m`)
|
|
163
169
|
})
|
|
164
170
|
|
|
165
171
|
program
|
package/src/report.tsx
CHANGED
|
@@ -49,18 +49,35 @@ function Header({ terminal, terminalVersion, os, osVersion, probeCount, category
|
|
|
49
49
|
<Text dimColor>Score</Text>
|
|
50
50
|
</Box>
|
|
51
51
|
<Box flexDirection="column">
|
|
52
|
-
<Text bold>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
<Text bold>
|
|
53
|
+
{terminal}
|
|
54
|
+
{terminalVersion ? ` ${terminalVersion}` : ""}
|
|
55
|
+
</Text>
|
|
56
|
+
<Text>
|
|
57
|
+
{os} {osVersion}
|
|
58
|
+
</Text>
|
|
59
|
+
<Text>
|
|
60
|
+
{probeCount} features, {categoryCount} categories
|
|
61
|
+
</Text>
|
|
62
|
+
<Text bold color={pct === 100 ? "green" : pct >= 90 ? "yellow" : "red"}>
|
|
63
|
+
{passed}/{total} ({pct}%)
|
|
64
|
+
</Text>
|
|
56
65
|
</Box>
|
|
57
66
|
</Box>
|
|
58
67
|
</Box>
|
|
59
68
|
)
|
|
60
69
|
}
|
|
61
70
|
|
|
62
|
-
function CategorySection({
|
|
63
|
-
|
|
71
|
+
function CategorySection({
|
|
72
|
+
name,
|
|
73
|
+
probes,
|
|
74
|
+
slugs,
|
|
75
|
+
}: {
|
|
76
|
+
name: string
|
|
77
|
+
probes: ProbeResult[]
|
|
78
|
+
slugs: Record<string, string>
|
|
79
|
+
}) {
|
|
80
|
+
const catPassed = probes.filter((p) => p.pass).length
|
|
64
81
|
const allPassed = catPassed === probes.length
|
|
65
82
|
const catUrl = `https://terminfo.dev/${name}`
|
|
66
83
|
|
|
@@ -69,7 +86,7 @@ function CategorySection({ name, probes, slugs }: { name: string; probes: ProbeR
|
|
|
69
86
|
<Text color={allPassed ? "green" : catPassed > 0 ? "yellow" : "red"}>
|
|
70
87
|
{osc8(catUrl, name)} ({catPassed}/{probes.length})
|
|
71
88
|
</Text>
|
|
72
|
-
{probes.map(p => {
|
|
89
|
+
{probes.map((p) => {
|
|
73
90
|
const slug = slugs[p.id] ?? p.id.replaceAll(".", "-")
|
|
74
91
|
const url = featureUrl(p.id, slug)
|
|
75
92
|
const icon = p.pass ? "✓" : "✗"
|
|
@@ -90,7 +107,7 @@ function Footer({ submitMode }: { submitMode: boolean }) {
|
|
|
90
107
|
return (
|
|
91
108
|
<Box flexDirection="column" marginTop={1}>
|
|
92
109
|
<Text dimColor>Submit: npx terminfo.dev --submit</Text>
|
|
93
|
-
<Text dimColor>JSON:
|
|
110
|
+
<Text dimColor>JSON: npx terminfo.dev --json</Text>
|
|
94
111
|
</Box>
|
|
95
112
|
)
|
|
96
113
|
}
|
|
@@ -107,7 +124,9 @@ function Report(props: ReportProps & { slugs: Record<string, string>; submitMode
|
|
|
107
124
|
)
|
|
108
125
|
}
|
|
109
126
|
|
|
110
|
-
export async function renderReport(
|
|
127
|
+
export async function renderReport(
|
|
128
|
+
props: ReportProps & { slugs: Record<string, string>; submitMode: boolean },
|
|
129
|
+
): Promise<string> {
|
|
111
130
|
const width = process.stdout.columns ?? 80
|
|
112
131
|
return renderString(<Report {...props} />, { width })
|
|
113
132
|
}
|
package/src/submit.ts
CHANGED
|
@@ -6,6 +6,7 @@ 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"
|
|
9
10
|
|
|
10
11
|
const REPO = "beorn/terminfo.dev"
|
|
11
12
|
|
|
@@ -20,7 +21,42 @@ interface SubmitData {
|
|
|
20
21
|
generated: string
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
async function prompt(question: string, defaultValue?: string): Promise<string> {
|
|
25
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
26
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : ""
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
29
|
+
rl.close()
|
|
30
|
+
resolve(answer.trim() || defaultValue || "")
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
export async function submitResults(data: SubmitData): Promise<string | null> {
|
|
36
|
+
// Confirm/fill terminal info
|
|
37
|
+
console.log(`\n\x1b[1mConfirm submission details:\x1b[0m`)
|
|
38
|
+
data.terminal = await prompt(" Terminal name", data.terminal)
|
|
39
|
+
data.terminalVersion = await prompt(" Terminal version", data.terminalVersion || undefined)
|
|
40
|
+
data.os = await prompt(" Operating system", data.os)
|
|
41
|
+
|
|
42
|
+
if (!data.terminalVersion) {
|
|
43
|
+
console.log(`\x1b[33m ⚠ No version specified — results will be less useful\x1b[0m`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for duplicates
|
|
47
|
+
if (hasGhCli()) {
|
|
48
|
+
const existing = checkDuplicate(data.terminal, data.terminalVersion, data.os)
|
|
49
|
+
if (existing) {
|
|
50
|
+
console.log(`\n\x1b[33m⚠ A submission already exists for ${data.terminal}${data.terminalVersion ? ` ${data.terminalVersion}` : ""} on ${data.os}:\x1b[0m`)
|
|
51
|
+
console.log(` ${existing}`)
|
|
52
|
+
const proceed = await prompt(" Submit anyway? (y/N)", "N")
|
|
53
|
+
if (proceed.toLowerCase() !== "y") {
|
|
54
|
+
console.log(`Skipped.`)
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
24
60
|
const passed = Object.values(data.results).filter(Boolean).length
|
|
25
61
|
const total = Object.keys(data.results).length
|
|
26
62
|
const pct = Math.round((passed / total) * 100)
|
|
@@ -51,7 +87,7 @@ ${JSON.stringify(data, null, 2)}
|
|
|
51
87
|
</details>
|
|
52
88
|
|
|
53
89
|
---
|
|
54
|
-
*Submitted via \`npx terminfo.dev\`*`
|
|
90
|
+
*Submitted via \`npx terminfo.dev submit\`*`
|
|
55
91
|
|
|
56
92
|
if (!hasGhCli()) {
|
|
57
93
|
const filename = `terminfo-${data.terminal}-${data.os}-${Date.now()}.json`
|
|
@@ -61,7 +97,6 @@ ${JSON.stringify(data, null, 2)}
|
|
|
61
97
|
return null
|
|
62
98
|
}
|
|
63
99
|
|
|
64
|
-
// Write body to temp file to avoid shell escaping issues
|
|
65
100
|
const bodyFile = join(tmpdir(), `terminfo-submit-${Date.now()}.md`)
|
|
66
101
|
try {
|
|
67
102
|
writeFileSync(bodyFile, body)
|
|
@@ -90,6 +125,25 @@ function hasGhCli(): boolean {
|
|
|
90
125
|
}
|
|
91
126
|
}
|
|
92
127
|
|
|
128
|
+
function checkDuplicate(terminal: string, version: string, os: string): string | null {
|
|
129
|
+
try {
|
|
130
|
+
const search = `[census] ${terminal}${version ? ` ${version}` : ""} on ${os}`
|
|
131
|
+
const result = execFileSync("gh", [
|
|
132
|
+
"issue", "list",
|
|
133
|
+
"--repo", REPO,
|
|
134
|
+
"--search", search,
|
|
135
|
+
"--state", "all",
|
|
136
|
+
"--limit", "1",
|
|
137
|
+
"--json", "url,title",
|
|
138
|
+
"--jq", ".[0] | .title + \" \" + .url",
|
|
139
|
+
], { encoding: "utf-8", timeout: 10000 })
|
|
140
|
+
const trimmed = result.trim()
|
|
141
|
+
return trimmed || null
|
|
142
|
+
} catch {
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
93
147
|
function formatSummary(data: SubmitData): string {
|
|
94
148
|
const categories = new Map<string, { pass: number; fail: number; failList: string[] }>()
|
|
95
149
|
for (const [id, pass] of Object.entries(data.results)) {
|