terminfo.dev 0.6.0 → 1.0.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 +4 -1
- package/src/index.ts +44 -30
- package/src/report.tsx +113 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminfo.dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Test your terminal's feature support and contribute to terminfo.dev",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ansi",
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"silvery": "^0.4.3"
|
|
37
|
+
},
|
|
35
38
|
"engines": {
|
|
36
39
|
"node": ">=23.6.0"
|
|
37
40
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,11 +13,37 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { readFileSync } from "node:fs"
|
|
17
|
+
import { dirname, join } from "node:path"
|
|
18
|
+
import { fileURLToPath } from "node:url"
|
|
16
19
|
import { detectTerminal } from "./detect.ts"
|
|
17
20
|
import { ALL_PROBES } from "./probes/index.ts"
|
|
18
21
|
import { withRawMode } from "./tty.ts"
|
|
19
22
|
import { submitResults } from "./submit.ts"
|
|
20
23
|
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
25
|
+
|
|
26
|
+
/** Load feature slugs from features.json for OSC 8 hyperlinks */
|
|
27
|
+
function loadFeatureSlugs(): Record<string, string> {
|
|
28
|
+
// Try repo-local path first, then npm-installed path
|
|
29
|
+
const candidates = [
|
|
30
|
+
join(__dirname, "..", "..", "features.json"), // repo: cli/src/ -> features.json
|
|
31
|
+
join(__dirname, "..", "..", "..", "features.json"), // npm: node_modules/terminfo.dev/src/ -> features.json
|
|
32
|
+
]
|
|
33
|
+
for (const path of candidates) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"))
|
|
36
|
+
delete raw.$comment
|
|
37
|
+
const slugs: Record<string, string> = {}
|
|
38
|
+
for (const [id, entry] of Object.entries(raw) as [string, any][]) {
|
|
39
|
+
slugs[id] = entry.slug ?? id.replaceAll(".", "-")
|
|
40
|
+
}
|
|
41
|
+
return slugs
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
return {} // fallback: featureSlug() will use id.replaceAll(".", "-")
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
interface ResultEntry {
|
|
22
48
|
terminal: string
|
|
23
49
|
terminalVersion: string
|
|
@@ -38,19 +64,6 @@ async function main() {
|
|
|
38
64
|
// Detect terminal
|
|
39
65
|
const terminal = detectTerminal()
|
|
40
66
|
|
|
41
|
-
if (!jsonMode) {
|
|
42
|
-
console.log(`\x1b[1mterminfo.dev\x1b[0m — can your terminal do that?\n`)
|
|
43
|
-
console.log(` Terminal: \x1b[1m${terminal.name}\x1b[0m${terminal.version ? ` ${terminal.version}` : ""}`)
|
|
44
|
-
console.log(` Platform: ${terminal.os}${terminal.osVersion ? ` ${terminal.osVersion}` : ""}`)
|
|
45
|
-
console.log(` Probes: ${ALL_PROBES.length} features across ${new Set(ALL_PROBES.map(p => p.id.split(".")[0])).size} categories`)
|
|
46
|
-
console.log(` Website: https://terminfo.dev`)
|
|
47
|
-
console.log(``)
|
|
48
|
-
console.log(`\x1b[2mResults are compared against ${ALL_PROBES.length} terminal features from the`)
|
|
49
|
-
console.log(`ECMA-48, VT100/VT510, xterm, and Kitty specifications.`)
|
|
50
|
-
console.log(`Run with --submit to contribute your results to the database.\x1b[0m\n`)
|
|
51
|
-
console.log(`Running probes...`)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
67
|
const results: Record<string, boolean> = {}
|
|
55
68
|
const notes: Record<string, string> = {}
|
|
56
69
|
const responses: Record<string, string> = {}
|
|
@@ -103,10 +116,8 @@ async function main() {
|
|
|
103
116
|
return
|
|
104
117
|
}
|
|
105
118
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Show categories
|
|
119
|
+
// Build category data for report
|
|
120
|
+
const slugs = loadFeatureSlugs()
|
|
110
121
|
const categories = new Map<string, Array<{ id: string; name: string; pass: boolean; note?: string }>>()
|
|
111
122
|
for (const probe of ALL_PROBES) {
|
|
112
123
|
const cat = probe.id.split(".")[0]!
|
|
@@ -119,16 +130,22 @@ async function main() {
|
|
|
119
130
|
})
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
// Render with silvery
|
|
134
|
+
const { renderReport } = await import("./report.tsx")
|
|
135
|
+
const output = await renderReport({
|
|
136
|
+
terminal: terminal.name,
|
|
137
|
+
terminalVersion: terminal.version,
|
|
138
|
+
os: terminal.os,
|
|
139
|
+
osVersion: terminal.osVersion,
|
|
140
|
+
probeCount: total,
|
|
141
|
+
categoryCount: new Set(ALL_PROBES.map(p => p.id.split(".")[0])).size,
|
|
142
|
+
passed,
|
|
143
|
+
total,
|
|
144
|
+
categories,
|
|
145
|
+
slugs,
|
|
146
|
+
submitMode,
|
|
147
|
+
})
|
|
148
|
+
console.log(output)
|
|
132
149
|
|
|
133
150
|
if (submitMode) {
|
|
134
151
|
console.log(`\nSubmitting results to terminfo.dev...`)
|
|
@@ -136,9 +153,6 @@ async function main() {
|
|
|
136
153
|
if (url) {
|
|
137
154
|
console.log(`\x1b[32m✓ Issue created:\x1b[0m ${url}`)
|
|
138
155
|
}
|
|
139
|
-
} else {
|
|
140
|
-
console.log(`\n\x1b[2mSubmit results to terminfo.dev: npx terminfo --submit\x1b[0m`)
|
|
141
|
-
console.log(`\x1b[2mJSON output: npx terminfo --json\x1b[0m`)
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
158
|
|
package/src/report.tsx
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Silvery-rendered CLI report for terminfo probe results.
|
|
3
|
+
*/
|
|
4
|
+
import React from "react"
|
|
5
|
+
import { Box, Text } from "silvery"
|
|
6
|
+
import { renderString } from "silvery"
|
|
7
|
+
|
|
8
|
+
interface ProbeResult {
|
|
9
|
+
id: string
|
|
10
|
+
name: string
|
|
11
|
+
pass: boolean
|
|
12
|
+
note?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ReportProps {
|
|
16
|
+
terminal: string
|
|
17
|
+
terminalVersion: string
|
|
18
|
+
os: string
|
|
19
|
+
osVersion: string
|
|
20
|
+
probeCount: number
|
|
21
|
+
categoryCount: number
|
|
22
|
+
passed: number
|
|
23
|
+
total: number
|
|
24
|
+
categories: Map<string, ProbeResult[]>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** OSC 8 hyperlink wrapper */
|
|
28
|
+
function osc8(url: string, text: string): string {
|
|
29
|
+
return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function featureUrl(id: string, slug: string): string {
|
|
33
|
+
const cat = id.split(".")[0]!
|
|
34
|
+
return `https://terminfo.dev/${cat}/${slug}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Header({ terminal, terminalVersion, os, osVersion, probeCount, categoryCount, passed, total }: ReportProps) {
|
|
38
|
+
const pct = Math.round((passed / total) * 100)
|
|
39
|
+
return (
|
|
40
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
41
|
+
<Text bold>{osc8("https://terminfo.dev", "terminfo.dev")}</Text>
|
|
42
|
+
<Text dimColor>Can your terminal do that?</Text>
|
|
43
|
+
<Text> </Text>
|
|
44
|
+
<Box flexDirection="row" gap={2}>
|
|
45
|
+
<Box flexDirection="column" width={12}>
|
|
46
|
+
<Text dimColor>Terminal</Text>
|
|
47
|
+
<Text dimColor>Platform</Text>
|
|
48
|
+
<Text dimColor>Probes</Text>
|
|
49
|
+
<Text dimColor>Score</Text>
|
|
50
|
+
</Box>
|
|
51
|
+
<Box flexDirection="column">
|
|
52
|
+
<Text bold>{terminal}{terminalVersion ? ` ${terminalVersion}` : ""}</Text>
|
|
53
|
+
<Text>{os} {osVersion}</Text>
|
|
54
|
+
<Text>{probeCount} features, {categoryCount} categories</Text>
|
|
55
|
+
<Text bold color={pct === 100 ? "green" : pct >= 90 ? "yellow" : "red"}>{passed}/{total} ({pct}%)</Text>
|
|
56
|
+
</Box>
|
|
57
|
+
</Box>
|
|
58
|
+
</Box>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function CategorySection({ name, probes, slugs }: { name: string; probes: ProbeResult[]; slugs: Record<string, string> }) {
|
|
63
|
+
const catPassed = probes.filter(p => p.pass).length
|
|
64
|
+
const allPassed = catPassed === probes.length
|
|
65
|
+
const catUrl = `https://terminfo.dev/${name}`
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Box flexDirection="column">
|
|
69
|
+
<Text color={allPassed ? "green" : catPassed > 0 ? "yellow" : "red"}>
|
|
70
|
+
{osc8(catUrl, name)} ({catPassed}/{probes.length})
|
|
71
|
+
</Text>
|
|
72
|
+
{probes.map(p => {
|
|
73
|
+
const slug = slugs[p.id] ?? p.id.replaceAll(".", "-")
|
|
74
|
+
const url = featureUrl(p.id, slug)
|
|
75
|
+
const icon = p.pass ? "✓" : "✗"
|
|
76
|
+
return (
|
|
77
|
+
<Box key={p.id} flexDirection="row" paddingLeft={2}>
|
|
78
|
+
<Text color={p.pass ? "green" : "red"}>{icon} </Text>
|
|
79
|
+
<Text>{osc8(url, p.name)}</Text>
|
|
80
|
+
{p.note && <Text dimColor> — {p.note}</Text>}
|
|
81
|
+
</Box>
|
|
82
|
+
)
|
|
83
|
+
})}
|
|
84
|
+
</Box>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Footer({ submitMode }: { submitMode: boolean }) {
|
|
89
|
+
if (submitMode) return null
|
|
90
|
+
return (
|
|
91
|
+
<Box flexDirection="column" marginTop={1}>
|
|
92
|
+
<Text dimColor>Submit: npx terminfo.dev --submit</Text>
|
|
93
|
+
<Text dimColor>JSON: npx terminfo.dev --json</Text>
|
|
94
|
+
</Box>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Report(props: ReportProps & { slugs: Record<string, string>; submitMode: boolean }) {
|
|
99
|
+
return (
|
|
100
|
+
<Box flexDirection="column">
|
|
101
|
+
<Header {...props} />
|
|
102
|
+
{[...props.categories.entries()].map(([name, probes]) => (
|
|
103
|
+
<CategorySection key={name} name={name} probes={probes} slugs={props.slugs} />
|
|
104
|
+
))}
|
|
105
|
+
<Footer submitMode={props.submitMode} />
|
|
106
|
+
</Box>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function renderReport(props: ReportProps & { slugs: Record<string, string>; submitMode: boolean }): Promise<string> {
|
|
111
|
+
const width = process.stdout.columns ?? 80
|
|
112
|
+
return renderString(<Report {...props} />, { width })
|
|
113
|
+
}
|