@adhdev/ghostty-vt-node 1.0.78 → 1.0.82-rc.38
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/CMakeLists.txt +284 -0
- package/README.md +151 -9
- package/binding-candidates.js +40 -0
- package/index.d.ts +107 -0
- package/index.js +92 -48
- package/package.json +6 -2
- package/prebuilt/darwin-arm64-node137/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node137/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node137/libghostty-vt.dylib +0 -0
- package/prebuilt/darwin-arm64-node137/manifest.json +34 -0
- package/prebuilt/linux-x64-node137/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node137/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node137/libghostty-vt.so.0 +0 -0
- package/prebuilt/linux-x64-node137/libghostty-vt.so.0.1.0 +0 -0
- package/prebuilt/linux-x64-node137/manifest.json +38 -0
- package/prebuilt/win32-x64-node137/ghostty-vt.dll +0 -0
- package/prebuilt/win32-x64-node137/ghostty_vt_node.node +0 -0
- package/prebuilt/win32-x64-node137/manifest.json +30 -0
- package/scripts/binding-digest.mjs +60 -0
- package/scripts/build-or-use-prebuilt.mjs +60 -31
- package/scripts/check-prebuilts.mjs +113 -0
- package/scripts/clean-dir.cjs +32 -0
- package/scripts/host-path-scan.mjs +115 -0
- package/scripts/native-format.mjs +219 -0
- package/scripts/prebuilt-manifest.mjs +231 -0
- package/scripts/probe-build-provenance.mjs +288 -0
- package/scripts/verify-prebuilt.mjs +115 -0
- package/src/addon.cc +403 -23
- package/src/ghostty_bridge.c +344 -0
- package/src/ghostty_bridge.h +96 -0
- package/toolchain.json +3 -0
- package/prebuilt/darwin-arm64-node115/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node115/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node115/libghostty-vt.dylib +0 -0
- package/prebuilt/darwin-arm64-node127/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node127/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node127/libghostty-vt.dylib +0 -0
- package/prebuilt/darwin-arm64-node141/ghostty_vt_node.node +0 -0
- package/prebuilt/darwin-arm64-node141/libghostty-vt.0.1.0.dylib +0 -0
- package/prebuilt/darwin-arm64-node141/libghostty-vt.dylib +0 -0
- package/prebuilt/linux-x64-node115/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node115/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node115/libghostty-vt.so.0 +0 -0
- package/prebuilt/linux-x64-node115/libghostty-vt.so.0.1.0 +0 -0
- package/prebuilt/linux-x64-node127/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node127/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node127/libghostty-vt.so.0 +0 -0
- package/prebuilt/linux-x64-node127/libghostty-vt.so.0.1.0 +0 -0
- package/prebuilt/linux-x64-node141/ghostty_vt_node.node +0 -0
- package/prebuilt/linux-x64-node141/libghostty-vt.so +0 -0
- package/prebuilt/linux-x64-node141/libghostty-vt.so.0 +0 -0
- package/prebuilt/linux-x64-node141/libghostty-vt.so.0.1.0 +0 -0
- package/prebuilt/win32-x64-node127/ghostty-vt.dll +0 -0
- package/prebuilt/win32-x64-node127/ghostty_vt_node.node +0 -0
package/CMakeLists.txt
CHANGED
|
@@ -16,21 +16,270 @@ execute_process(
|
|
|
16
16
|
string(REPLACE "\"" "" NODE_ADDON_API_DIR "${NODE_ADDON_API_DIR}")
|
|
17
17
|
|
|
18
18
|
set(GHOSTTY_GIT_TAG "d4a38c8661453026bd1b1bb022ba2c37b347adce" CACHE STRING "Ghostty git revision for libghostty-vt")
|
|
19
|
+
# The pin itself, independent of the overridable cache entry above.
|
|
20
|
+
set(ADHDEV_GHOSTTY_PINNED_REVISION "d4a38c8661453026bd1b1bb022ba2c37b347adce")
|
|
19
21
|
|
|
20
22
|
if(DEFINED ENV{ZIG_EXECUTABLE} AND NOT DEFINED ZIG_EXECUTABLE)
|
|
21
23
|
set(ZIG_EXECUTABLE "$ENV{ZIG_EXECUTABLE}" CACHE FILEPATH "Path to zig executable")
|
|
22
24
|
endif()
|
|
23
25
|
|
|
26
|
+
# --- Build provenance ---------------------------------------------------------
|
|
27
|
+
# A release binary must be built from exactly the pinned ghostty revision, in a
|
|
28
|
+
# clean FetchContent-managed clone, with the pinned zig, and its libghostty-vt
|
|
29
|
+
# must be built by THIS build from fresh, private zig caches (see "Runtime
|
|
30
|
+
# freshness" below). Each violation
|
|
31
|
+
# is a problem code; any problem fails the configure step unless the explicit
|
|
32
|
+
# developer override ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD=1 (cache variable or
|
|
33
|
+
# environment) is set. The override ALWAYS makes the build non-release, even
|
|
34
|
+
# with nothing else wrong: the binary reports releaseEligible=false and the
|
|
35
|
+
# prebuilt release gates refuse it. Only problem codes are embedded, never paths.
|
|
36
|
+
#
|
|
37
|
+
# toolchain.json is the single zig pin: the native workflow installs that
|
|
38
|
+
# version and the release gate compares against it. It is a binding digest
|
|
39
|
+
# input, so changing the pin invalidates every existing prebuilt.
|
|
40
|
+
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/toolchain.json" ADHDEV_TOOLCHAIN_JSON)
|
|
41
|
+
string(JSON ADHDEV_PINNED_ZIG_VERSION GET "${ADHDEV_TOOLCHAIN_JSON}" zig)
|
|
42
|
+
|
|
43
|
+
set(ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD OFF CACHE BOOL "Allow a non-release build from an unverified ghostty source or toolchain")
|
|
44
|
+
if("$ENV{ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD}" STREQUAL "1")
|
|
45
|
+
set(ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD ON)
|
|
46
|
+
endif()
|
|
47
|
+
set(ADHDEV_PROVENANCE_PROBLEMS "")
|
|
48
|
+
if(ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD)
|
|
49
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "unverified-dev-build")
|
|
50
|
+
endif()
|
|
51
|
+
if(NOT GHOSTTY_GIT_TAG STREQUAL ADHDEV_GHOSTTY_PINNED_REVISION)
|
|
52
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "ghostty-tag-override")
|
|
53
|
+
endif()
|
|
54
|
+
# A substituted checkout is not under this build's control (it may be shared,
|
|
55
|
+
# reused or written to while building). Release builds use the managed clone.
|
|
56
|
+
if(FETCHCONTENT_SOURCE_DIR_GHOSTTY)
|
|
57
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "ghostty-source-override")
|
|
58
|
+
endif()
|
|
59
|
+
|
|
60
|
+
# User-supplied extra zig flags change the library ghostty builds.
|
|
61
|
+
if(NOT "${GHOSTTY_ZIG_BUILD_FLAGS}" STREQUAL "")
|
|
62
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "extra-zig-build-flags")
|
|
63
|
+
endif()
|
|
64
|
+
|
|
65
|
+
# Resolve zig and record the version that actually builds libghostty-vt. The whole output, with
|
|
66
|
+
# only surrounding whitespace/CRLF trimmed, must equal the pin exactly.
|
|
67
|
+
find_program(ZIG_EXECUTABLE zig REQUIRED)
|
|
68
|
+
execute_process(
|
|
69
|
+
COMMAND "${ZIG_EXECUTABLE}" version
|
|
70
|
+
OUTPUT_VARIABLE zig_version_output
|
|
71
|
+
RESULT_VARIABLE zig_version_result
|
|
72
|
+
ERROR_QUIET
|
|
73
|
+
)
|
|
74
|
+
string(STRIP "${zig_version_output}" zig_version_output)
|
|
75
|
+
if(zig_version_result EQUAL 0 AND zig_version_output STREQUAL ADHDEV_PINNED_ZIG_VERSION)
|
|
76
|
+
set(ADHDEV_ZIG_VERSION "${ADHDEV_PINNED_ZIG_VERSION}")
|
|
77
|
+
else()
|
|
78
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "zig-version-mismatch")
|
|
79
|
+
# Recorded for diagnostics only, and only if it is a plain version token.
|
|
80
|
+
if(zig_version_output MATCHES "^[0-9A-Za-z.+-]+$")
|
|
81
|
+
set(ADHDEV_ZIG_VERSION "${zig_version_output}")
|
|
82
|
+
else()
|
|
83
|
+
set(ADHDEV_ZIG_VERSION "unrecognized")
|
|
84
|
+
endif()
|
|
85
|
+
endif()
|
|
86
|
+
|
|
87
|
+
# Only the SOURCE is taken from the clone. Upstream's CMakeLists.txt is not
|
|
88
|
+
# added (SOURCE_SUBDIR points at a directory without one): it builds through an
|
|
89
|
+
# OUTPUT-only custom command with the ambient zig caches and installs into the
|
|
90
|
+
# git-ignored zig-out/ of the source tree, none of which this build can vouch
|
|
91
|
+
# for. libghostty-vt is built by adhdev_ghostty_runtime below instead.
|
|
24
92
|
FetchContent_Declare(ghostty
|
|
25
93
|
GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git
|
|
26
94
|
GIT_TAG ${GHOSTTY_GIT_TAG}
|
|
95
|
+
SOURCE_SUBDIR adhdev-no-upstream-cmake
|
|
27
96
|
)
|
|
28
97
|
FetchContent_MakeAvailable(ghostty)
|
|
29
98
|
|
|
99
|
+
# The DECLARED tag proves nothing: FETCHCONTENT_SOURCE_DIR_GHOSTTY can point at
|
|
100
|
+
# any checkout. Verify the source that is actually built.
|
|
101
|
+
find_package(Git QUIET)
|
|
102
|
+
find_program(ADHDEV_NODE_EXECUTABLE node REQUIRED)
|
|
103
|
+
set(ADHDEV_GHOSTTY_SOURCE_REVISION "")
|
|
104
|
+
set(ADHDEV_GHOSTTY_SOURCE_CLEAN 0)
|
|
105
|
+
if(GIT_EXECUTABLE)
|
|
106
|
+
execute_process(
|
|
107
|
+
COMMAND "${GIT_EXECUTABLE}" -C "${ghostty_SOURCE_DIR}" rev-parse HEAD
|
|
108
|
+
OUTPUT_VARIABLE ghostty_head
|
|
109
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
110
|
+
RESULT_VARIABLE ghostty_head_result
|
|
111
|
+
ERROR_QUIET
|
|
112
|
+
)
|
|
113
|
+
execute_process(
|
|
114
|
+
COMMAND "${GIT_EXECUTABLE}" -C "${ghostty_SOURCE_DIR}" status --porcelain
|
|
115
|
+
OUTPUT_VARIABLE ghostty_status
|
|
116
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
117
|
+
RESULT_VARIABLE ghostty_status_result
|
|
118
|
+
ERROR_QUIET
|
|
119
|
+
)
|
|
120
|
+
if(ghostty_head_result EQUAL 0 AND ghostty_head MATCHES "^[0-9a-f]+$")
|
|
121
|
+
set(ADHDEV_GHOSTTY_SOURCE_REVISION "${ghostty_head}")
|
|
122
|
+
endif()
|
|
123
|
+
if(ghostty_status_result EQUAL 0 AND ghostty_status STREQUAL "")
|
|
124
|
+
set(ADHDEV_GHOSTTY_SOURCE_CLEAN 1)
|
|
125
|
+
endif()
|
|
126
|
+
endif()
|
|
127
|
+
if(ADHDEV_GHOSTTY_SOURCE_REVISION STREQUAL "")
|
|
128
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "ghostty-source-not-a-git-checkout")
|
|
129
|
+
elseif(NOT ADHDEV_GHOSTTY_SOURCE_REVISION STREQUAL ADHDEV_GHOSTTY_PINNED_REVISION)
|
|
130
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "ghostty-head-mismatch")
|
|
131
|
+
endif()
|
|
132
|
+
if(NOT ADHDEV_GHOSTTY_SOURCE_CLEAN)
|
|
133
|
+
list(APPEND ADHDEV_PROVENANCE_PROBLEMS "ghostty-source-dirty")
|
|
134
|
+
endif()
|
|
135
|
+
|
|
136
|
+
set(ADHDEV_VERIFY_GHOSTTY_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/adhdev-verify-ghostty-source.cmake")
|
|
137
|
+
file(WRITE "${ADHDEV_VERIFY_GHOSTTY_SCRIPT}" [==[
|
|
138
|
+
# Generated by ghostty-vt-node/CMakeLists.txt: re-verify at build time that the
|
|
139
|
+
# ghostty source is still the pinned revision with a clean tree.
|
|
140
|
+
execute_process(COMMAND "${GIT}" -C "${SRC}" rev-parse HEAD
|
|
141
|
+
OUTPUT_VARIABLE head OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE head_result ERROR_QUIET)
|
|
142
|
+
execute_process(COMMAND "${GIT}" -C "${SRC}" status --porcelain
|
|
143
|
+
OUTPUT_VARIABLE status OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE status_result ERROR_QUIET)
|
|
144
|
+
if(NOT head_result EQUAL 0 OR NOT head STREQUAL PIN)
|
|
145
|
+
message(FATAL_ERROR "ghostty-vt-node: ghostty source HEAD is '${head}', not the pin ${PIN}; reconfigure")
|
|
146
|
+
endif()
|
|
147
|
+
if(NOT status_result EQUAL 0 OR NOT status STREQUAL "")
|
|
148
|
+
message(FATAL_ERROR "ghostty-vt-node: ghostty source tree changed since configure; reconfigure from a clean clone")
|
|
149
|
+
endif()
|
|
150
|
+
]==])
|
|
151
|
+
|
|
152
|
+
if(ADHDEV_PROVENANCE_PROBLEMS)
|
|
153
|
+
if(NOT ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD)
|
|
154
|
+
message(FATAL_ERROR
|
|
155
|
+
"ghostty-vt-node build provenance check failed: ${ADHDEV_PROVENANCE_PROBLEMS}\n"
|
|
156
|
+
" ghostty pin ${ADHDEV_GHOSTTY_PINNED_REVISION}, resolved '${ADHDEV_GHOSTTY_SOURCE_REVISION}' (clean=${ADHDEV_GHOSTTY_SOURCE_CLEAN})\n"
|
|
157
|
+
" zig pin ${ADHDEV_PINNED_ZIG_VERSION} (toolchain.json), found '${ADHDEV_ZIG_VERSION}'\n"
|
|
158
|
+
"Fix the toolchain/source, or set ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD=1 for a local NON-RELEASE build "
|
|
159
|
+
"(the prebuilt release gates refuse it).")
|
|
160
|
+
endif()
|
|
161
|
+
message(WARNING "ghostty-vt-node: NON-RELEASE build (${ADHDEV_PROVENANCE_PROBLEMS})")
|
|
162
|
+
set(ADHDEV_GHOSTTY_RELEASE_ELIGIBLE 0)
|
|
163
|
+
else()
|
|
164
|
+
set(ADHDEV_GHOSTTY_RELEASE_ELIGIBLE 1)
|
|
165
|
+
endif()
|
|
166
|
+
string(REPLACE ";" "," ADHDEV_PROVENANCE_PROBLEMS_TEXT "${ADHDEV_PROVENANCE_PROBLEMS}")
|
|
167
|
+
message(STATUS "ghostty-vt-node provenance: ghostty ${ADHDEV_GHOSTTY_SOURCE_REVISION} clean=${ADHDEV_GHOSTTY_SOURCE_CLEAN} zig ${ADHDEV_ZIG_VERSION} release=${ADHDEV_GHOSTTY_RELEASE_ELIGIBLE}")
|
|
168
|
+
|
|
169
|
+
# --- Runtime freshness --------------------------------------------------------
|
|
170
|
+
# HEAD/status/zig checks describe the INPUTS; the runtime must also be built
|
|
171
|
+
# from them and nothing else. Everything zig reads or writes besides the
|
|
172
|
+
# verified source and the pinned zig lives in this build tree:
|
|
173
|
+
# ghostty-zig/global-cache zig's global cache, incl. fetched packages (p/)
|
|
174
|
+
# ghostty-zig/local-cache zig's local cache (compiled artifacts)
|
|
175
|
+
# ghostty-zig/prefix the install prefix the addon links and ships
|
|
176
|
+
# They are passed as --global-cache-dir / --cache-dir / --prefix, which take
|
|
177
|
+
# precedence over ZIG_GLOBAL_CACHE_DIR / ZIG_LOCAL_CACHE_DIR, so ambient caches
|
|
178
|
+
# are never read: overriding them (rather than refusing a build with them set)
|
|
179
|
+
# keeps CI and developer environments working while removing their influence.
|
|
180
|
+
# Zig accepts an existing package directory in a cache without re-hashing it,
|
|
181
|
+
# and reuses cache hits as-is, so for a RELEASE build all three are deleted at
|
|
182
|
+
# every configure: dependencies are fetched afresh (zig verifies each package
|
|
183
|
+
# hash on fetch) and everything is compiled from scratch. A non-release
|
|
184
|
+
# (ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD) build keeps them between configures.
|
|
185
|
+
#
|
|
186
|
+
# adhdev_ghostty_runtime runs `zig build` on EVERY build, before the addon links
|
|
187
|
+
# and copies the runtime, so a library left in the prefix is re-installed from
|
|
188
|
+
# this build's cache; for a release build it re-verifies the pinned, clean
|
|
189
|
+
# source before and after.
|
|
190
|
+
#
|
|
191
|
+
# LIMITATION: these checks are check-then-use, not atomic. A release build
|
|
192
|
+
# assumes exclusive ownership of the build tree, the ghostty clone and the zig
|
|
193
|
+
# installation for the whole configure + build: a concurrent writer could
|
|
194
|
+
# change an input between a check and its use. Build release prebuilts on a
|
|
195
|
+
# machine and in directories no other process writes to.
|
|
196
|
+
set(ADHDEV_GHOSTTY_ZIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/ghostty-zig")
|
|
197
|
+
set(ADHDEV_ZIG_GLOBAL_CACHE "${ADHDEV_GHOSTTY_ZIG_DIR}/global-cache")
|
|
198
|
+
set(ADHDEV_ZIG_LOCAL_CACHE "${ADHDEV_GHOSTTY_ZIG_DIR}/local-cache")
|
|
199
|
+
set(ADHDEV_GHOSTTY_PREFIX "${ADHDEV_GHOSTTY_ZIG_DIR}/prefix")
|
|
200
|
+
if(ADHDEV_GHOSTTY_RELEASE_ELIGIBLE)
|
|
201
|
+
# Not file(REMOVE_RECURSE): on Windows it stops part-way on names Win32
|
|
202
|
+
# normalizes away (zig extracts macOS `._.` files into the package cache),
|
|
203
|
+
# silently keeping cached packages. scripts/clean-dir.cjs removes them, and a
|
|
204
|
+
# clean that leaves anything behind fails the configure; a release build never
|
|
205
|
+
# proceeds on a partially emptied cache.
|
|
206
|
+
execute_process(
|
|
207
|
+
COMMAND "${ADHDEV_NODE_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/clean-dir.cjs" "${ADHDEV_GHOSTTY_ZIG_DIR}"
|
|
208
|
+
RESULT_VARIABLE adhdev_clean_result
|
|
209
|
+
OUTPUT_VARIABLE adhdev_clean_output
|
|
210
|
+
ERROR_VARIABLE adhdev_clean_output
|
|
211
|
+
)
|
|
212
|
+
if(NOT adhdev_clean_result EQUAL 0 OR EXISTS "${ADHDEV_GHOSTTY_ZIG_DIR}")
|
|
213
|
+
message(FATAL_ERROR
|
|
214
|
+
"ghostty-vt-node: could not empty the private zig caches in ${ADHDEV_GHOSTTY_ZIG_DIR} "
|
|
215
|
+
"(exit ${adhdev_clean_result}): ${adhdev_clean_output}\n"
|
|
216
|
+
"A release build must start from empty caches. Remove the directory by hand, "
|
|
217
|
+
"and make sure no other process is using it.")
|
|
218
|
+
endif()
|
|
219
|
+
endif()
|
|
220
|
+
file(MAKE_DIRECTORY "${ADHDEV_ZIG_GLOBAL_CACHE}" "${ADHDEV_ZIG_LOCAL_CACHE}" "${ADHDEV_GHOSTTY_PREFIX}/include")
|
|
221
|
+
if(DEFINED ENV{ZIG_GLOBAL_CACHE_DIR} OR DEFINED ENV{ZIG_LOCAL_CACHE_DIR})
|
|
222
|
+
message(STATUS "ghostty-vt-node: ZIG_GLOBAL_CACHE_DIR / ZIG_LOCAL_CACHE_DIR are ignored; zig uses the private caches in ${ADHDEV_GHOSTTY_ZIG_DIR}")
|
|
223
|
+
endif()
|
|
224
|
+
|
|
225
|
+
# Upstream's zig flags: build types with optimization map to ReleaseFast.
|
|
226
|
+
set(ADHDEV_GHOSTTY_ZIG_FLAGS -Demit-lib-vt ${GHOSTTY_ZIG_BUILD_FLAGS})
|
|
227
|
+
string(TOUPPER "${CMAKE_BUILD_TYPE}" adhdev_build_type)
|
|
228
|
+
if(adhdev_build_type STREQUAL "RELEASE" OR adhdev_build_type STREQUAL "MINSIZEREL" OR adhdev_build_type STREQUAL "RELWITHDEBINFO")
|
|
229
|
+
list(APPEND ADHDEV_GHOSTTY_ZIG_FLAGS "-Doptimize=ReleaseFast")
|
|
230
|
+
endif()
|
|
231
|
+
|
|
232
|
+
# The library names upstream's CMake wrapper uses at the pinned revision.
|
|
233
|
+
if(APPLE)
|
|
234
|
+
set(GHOSTTY_VT_SONAME "libghostty-vt.0.dylib")
|
|
235
|
+
set(GHOSTTY_VT_SHARED_LIBRARY "${ADHDEV_GHOSTTY_PREFIX}/lib/libghostty-vt.0.1.0.dylib")
|
|
236
|
+
elseif(WIN32)
|
|
237
|
+
set(GHOSTTY_VT_SHARED_LIBRARY "${ADHDEV_GHOSTTY_PREFIX}/bin/ghostty-vt.dll")
|
|
238
|
+
set(GHOSTTY_VT_IMPLIB "${ADHDEV_GHOSTTY_PREFIX}/lib/ghostty-vt.lib")
|
|
239
|
+
else()
|
|
240
|
+
set(GHOSTTY_VT_SONAME "libghostty-vt.so.0")
|
|
241
|
+
set(GHOSTTY_VT_SHARED_LIBRARY "${ADHDEV_GHOSTTY_PREFIX}/lib/libghostty-vt.so.0.1.0")
|
|
242
|
+
endif()
|
|
243
|
+
|
|
244
|
+
if(ADHDEV_GHOSTTY_RELEASE_ELIGIBLE)
|
|
245
|
+
set(ADHDEV_VERIFY_GHOSTTY_COMMAND
|
|
246
|
+
"${CMAKE_COMMAND}" "-DGIT=${GIT_EXECUTABLE}" "-DSRC=${ghostty_SOURCE_DIR}"
|
|
247
|
+
"-DPIN=${ADHDEV_GHOSTTY_PINNED_REVISION}" -P "${ADHDEV_VERIFY_GHOSTTY_SCRIPT}")
|
|
248
|
+
else()
|
|
249
|
+
set(ADHDEV_VERIFY_GHOSTTY_COMMAND "${CMAKE_COMMAND}" -E echo "ghostty-vt-node: non-release build, source not re-verified")
|
|
250
|
+
endif()
|
|
251
|
+
add_custom_target(adhdev_ghostty_runtime
|
|
252
|
+
COMMAND ${ADHDEV_VERIFY_GHOSTTY_COMMAND}
|
|
253
|
+
COMMAND "${ZIG_EXECUTABLE}" build ${ADHDEV_GHOSTTY_ZIG_FLAGS}
|
|
254
|
+
--prefix "${ADHDEV_GHOSTTY_PREFIX}"
|
|
255
|
+
--cache-dir "${ADHDEV_ZIG_LOCAL_CACHE}"
|
|
256
|
+
--global-cache-dir "${ADHDEV_ZIG_GLOBAL_CACHE}"
|
|
257
|
+
COMMAND ${ADHDEV_VERIFY_GHOSTTY_COMMAND}
|
|
258
|
+
BYPRODUCTS "${GHOSTTY_VT_SHARED_LIBRARY}" ${GHOSTTY_VT_IMPLIB}
|
|
259
|
+
WORKING_DIRECTORY "${ghostty_SOURCE_DIR}"
|
|
260
|
+
COMMENT "Building libghostty-vt with zig (always; private caches and prefix)"
|
|
261
|
+
VERBATIM
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
add_library(ghostty-vt SHARED IMPORTED GLOBAL)
|
|
265
|
+
set_target_properties(ghostty-vt PROPERTIES
|
|
266
|
+
IMPORTED_LOCATION "${GHOSTTY_VT_SHARED_LIBRARY}"
|
|
267
|
+
INTERFACE_INCLUDE_DIRECTORIES "${ADHDEV_GHOSTTY_PREFIX}/include"
|
|
268
|
+
)
|
|
269
|
+
if(APPLE)
|
|
270
|
+
set_target_properties(ghostty-vt PROPERTIES IMPORTED_SONAME "@rpath/${GHOSTTY_VT_SONAME}")
|
|
271
|
+
elseif(WIN32)
|
|
272
|
+
set_target_properties(ghostty-vt PROPERTIES IMPORTED_IMPLIB "${GHOSTTY_VT_IMPLIB}")
|
|
273
|
+
else()
|
|
274
|
+
set_target_properties(ghostty-vt PROPERTIES IMPORTED_SONAME "${GHOSTTY_VT_SONAME}")
|
|
275
|
+
endif()
|
|
276
|
+
add_dependencies(ghostty-vt adhdev_ghostty_runtime)
|
|
277
|
+
|
|
30
278
|
add_library(${PROJECT_NAME} SHARED
|
|
31
279
|
src/addon.cc
|
|
32
280
|
src/ghostty_bridge.c
|
|
33
281
|
)
|
|
282
|
+
add_dependencies(${PROJECT_NAME} adhdev_ghostty_runtime)
|
|
34
283
|
|
|
35
284
|
set(GHOSTTY_VT_RUNTIME_ALIAS "${CMAKE_SHARED_LIBRARY_PREFIX}ghostty-vt${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
|
36
285
|
|
|
@@ -39,9 +288,44 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
|
|
39
288
|
${NODE_ADDON_API_DIR}
|
|
40
289
|
)
|
|
41
290
|
|
|
291
|
+
# Binding source digest, embedded so a compiled binary can be matched against
|
|
292
|
+
# the sources it was built from. scripts/binding-digest.mjs implements the same
|
|
293
|
+
# algorithm: per file "<path>:<sha256 of LF-normalized content>\n", in this
|
|
294
|
+
# order, then sha256 of the concatenation. Reconfigure whenever an input
|
|
295
|
+
# changes so an incremental compile never embeds a stale digest.
|
|
296
|
+
set(ADHDEV_BINDING_DIGEST_INPUTS
|
|
297
|
+
CMakeLists.txt
|
|
298
|
+
toolchain.json
|
|
299
|
+
src/addon.cc
|
|
300
|
+
src/ghostty_bridge.c
|
|
301
|
+
src/ghostty_bridge.h
|
|
302
|
+
scripts/clean-dir.cjs
|
|
303
|
+
)
|
|
304
|
+
set(ADHDEV_BINDING_DIGEST_MANIFEST "")
|
|
305
|
+
foreach(input IN LISTS ADHDEV_BINDING_DIGEST_INPUTS)
|
|
306
|
+
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/${input}" input_content)
|
|
307
|
+
string(REPLACE "\r\n" "\n" input_content "${input_content}")
|
|
308
|
+
string(SHA256 input_hash "${input_content}")
|
|
309
|
+
string(APPEND ADHDEV_BINDING_DIGEST_MANIFEST "${input}:${input_hash}\n")
|
|
310
|
+
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${input}")
|
|
311
|
+
endforeach()
|
|
312
|
+
string(SHA256 ADHDEV_BINDING_SOURCE_DIGEST "${ADHDEV_BINDING_DIGEST_MANIFEST}")
|
|
313
|
+
message(STATUS "ghostty-vt-node binding source digest: ${ADHDEV_BINDING_SOURCE_DIGEST}")
|
|
314
|
+
|
|
42
315
|
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
|
43
316
|
NAPI_DISABLE_CPP_EXCEPTIONS
|
|
317
|
+
NAPI_VERSION=8
|
|
44
318
|
GHOSTTY_STATIC
|
|
319
|
+
ADHDEV_GHOSTTY_SOURCE_DIGEST="${ADHDEV_BINDING_SOURCE_DIGEST}"
|
|
320
|
+
ADHDEV_GHOSTTY_GIT_TAG="${GHOSTTY_GIT_TAG}"
|
|
321
|
+
ADHDEV_GHOSTTY_RELEASE_ELIGIBLE=${ADHDEV_GHOSTTY_RELEASE_ELIGIBLE}
|
|
322
|
+
ADHDEV_GHOSTTY_PROVENANCE_PROBLEMS="${ADHDEV_PROVENANCE_PROBLEMS_TEXT}"
|
|
323
|
+
ADHDEV_GHOSTTY_SOURCE_REVISION="${ADHDEV_GHOSTTY_SOURCE_REVISION}"
|
|
324
|
+
ADHDEV_GHOSTTY_SOURCE_CLEAN=${ADHDEV_GHOSTTY_SOURCE_CLEAN}
|
|
325
|
+
ADHDEV_GHOSTTY_ZIG_VERSION="${ADHDEV_ZIG_VERSION}"
|
|
326
|
+
ADHDEV_GHOSTTY_C_COMPILER="${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}"
|
|
327
|
+
ADHDEV_GHOSTTY_CXX_COMPILER="${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}"
|
|
328
|
+
ADHDEV_GHOSTTY_BUILD_TYPE="${CMAKE_BUILD_TYPE}"
|
|
45
329
|
)
|
|
46
330
|
|
|
47
331
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
package/README.md
CHANGED
|
@@ -52,28 +52,170 @@ Unable to load @adhdev/ghostty-vt-node native binding for running triplet
|
|
|
52
52
|
linux-x64-node137, win32-x64-node137]. ...
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
## Viewport / cursor / cell reads (API level 2)
|
|
56
|
+
|
|
57
|
+
Each terminal handle also exposes an exact-read API. All rows and the cursor are
|
|
58
|
+
in an explicit coordinate space; nothing is derived from formatter strings.
|
|
59
|
+
|
|
60
|
+
- `getViewport({ includeRows: 'none' | 'cursor' | 'all', cells })` — one native
|
|
61
|
+
snapshot: `cursorRow`/`cursorCol` (ACTIVE space), `activeScreen`
|
|
62
|
+
(`'primary'`/`'alternate'`), `scrollbar` (`total`/`offset`/`len`), the derived
|
|
63
|
+
`scrollbackRows` / `activeOriginRow` / `viewportOriginRow` /
|
|
64
|
+
`viewportScrollbackOffset` / `cursorViewportRow`, a per-terminal `revision`,
|
|
65
|
+
and the requested ACTIVE rows. Metadata and rows come from the same call.
|
|
66
|
+
- `readRow(row, { space: 'active' | 'viewport', cells, expectedRevision })` — one
|
|
67
|
+
row with `wrapped` / `wrapContinuation`; with `cells`, exactly `cols` cells of
|
|
68
|
+
`{ col, text, width, wideKind }` (`text` is the full stored grapheme).
|
|
69
|
+
- `scrollViewport('top' | 'bottom' | delta)` — moves the VIEWPORT only; ACTIVE
|
|
70
|
+
rows and `cursorRow` do not change.
|
|
71
|
+
|
|
72
|
+
**ACTIVE vs VIEWPORT.** ACTIVE is the rows x cols area the cursor moves in.
|
|
73
|
+
VIEWPORT is what is visible and differs only while scrolled into history.
|
|
74
|
+
`formatPlainText()` output is SCREEN-origin (history + active, trailing blank
|
|
75
|
+
rows dropped): the cursor's row is at index `activeOriginRow + cursorRow` there,
|
|
76
|
+
not at `cursorRow`. The legacy `formatPlainText` / `formatVT` /
|
|
77
|
+
`getCursorPosition` outputs are unchanged (see `test/fixtures/legacy-golden.json`).
|
|
78
|
+
|
|
79
|
+
**Feature detection.** A binary built before API level 2 still loads and serves
|
|
80
|
+
the legacy methods but has no `getViewport`/`readRow`/`scrollViewport`. Check
|
|
81
|
+
`require('@adhdev/ghostty-vt-node').capabilities.viewport` (or `typeof
|
|
82
|
+
handle.getViewport === 'function'`) and treat exact reads as unavailable —
|
|
83
|
+
never fall back to guessing from formatter output. `bindingInfo` reports the
|
|
84
|
+
loaded binary's `apiLevel`, embedded `sourceDigest` and `ghosttyRevision`.
|
|
85
|
+
|
|
86
|
+
Every method on a terminal handle owns a share of the native state, so a method
|
|
87
|
+
taken off the handle (`const get = t.getViewport`) keeps working after the
|
|
88
|
+
handle is garbage-collected; the terminal is freed when the last of them is.
|
|
89
|
+
After `dispose()` every method throws `ERR_GHOSTTY_DISPOSED`.
|
|
90
|
+
|
|
55
91
|
## Producing / refreshing a prebuilt
|
|
56
92
|
|
|
57
93
|
Build on the **target platform/arch** (cross-builds are not wired here) with the
|
|
58
|
-
toolchain present (`cmake`, a C/C++ compiler, and
|
|
59
|
-
`ZIG_EXECUTABLE` if zig is not on
|
|
94
|
+
toolchain present (`cmake`, `git`, a C/C++ compiler, and the zig version pinned
|
|
95
|
+
in `toolchain.json` for `libghostty`; set `ZIG_EXECUTABLE` if zig is not on
|
|
96
|
+
`PATH`).
|
|
97
|
+
|
|
98
|
+
The configure step verifies the build's provenance and fails on any problem:
|
|
99
|
+
ghostty must come from the FetchContent-managed clone (a
|
|
100
|
+
`FETCHCONTENT_SOURCE_DIR_GHOSTTY` substitute or a `GHOSTTY_GIT_TAG` override is
|
|
101
|
+
refused) at the pinned revision with a clean tree; `zig version` must print
|
|
102
|
+
exactly the `toolchain.json` version (only surrounding whitespace is trimmed);
|
|
103
|
+
and `GHOSTTY_ZIG_BUILD_FLAGS` must be empty. Upstream's CMake wrapper is not
|
|
104
|
+
used: the `adhdev_ghostty_runtime` target runs `zig build` itself on every
|
|
105
|
+
build, with a private global cache, local cache and install prefix inside the
|
|
106
|
+
build tree (`build/ghostty-zig/`; passed as `--global-cache-dir`, `--cache-dir`
|
|
107
|
+
and `--prefix`, so `ZIG_GLOBAL_CACHE_DIR` / `ZIG_LOCAL_CACHE_DIR` are ignored),
|
|
108
|
+
and re-verifies the pinned, clean source before and after. For a release build
|
|
109
|
+
configure empties those caches first (with `scripts/clean-dir.cjs`, because
|
|
110
|
+
CMake's own removal stops on the `._.` files zig extracts on Windows; if
|
|
111
|
+
anything survives, configure fails), so every dependency is fetched afresh
|
|
112
|
+
(zig verifies its package hash on fetch) and everything is compiled from
|
|
113
|
+
scratch: a stale `zig-out/`, a reused build directory or a poisoned shared zig
|
|
114
|
+
cache cannot reach the shipped runtime. What this does NOT cover: the zig
|
|
115
|
+
installation itself (only its version is checked) and concurrent writers.
|
|
116
|
+
The checks are check-then-use, so a release build needs exclusive ownership of
|
|
117
|
+
its build tree, the ghostty clone and the zig installation for the whole
|
|
118
|
+
configure + build; build release prebuilts where no other process writes to
|
|
119
|
+
them. For local experiments only, `ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD=1` turns
|
|
120
|
+
a failure into a warning, and ALWAYS makes the build non-release: the binary
|
|
121
|
+
reports `releaseEligible: false` with its problem codes (at least
|
|
122
|
+
`unverified-dev-build`), and every release gate refuses it (`non-release`). The
|
|
123
|
+
addon also records the zig version, C/C++ compiler id and version, and build
|
|
124
|
+
type (`getBindingInfo()`, copied into the manifest).
|
|
125
|
+
`scripts/probe-build-provenance.mjs <dir>` exercises all of this against a
|
|
126
|
+
configured build in `<dir>/build`: stale runtime, poisoned ambient and private
|
|
127
|
+
zig caches (including a control showing the poison breaks a build that keeps
|
|
128
|
+
them), reconfigure, source edit, dev override, zig output parsing and source
|
|
129
|
+
override. It rebuilds twice from empty caches, so it needs network access and
|
|
130
|
+
about 1 GiB of free disk. Two fresh builds differ only in per-build debug
|
|
131
|
+
metadata (cache object paths, mtimes, image id, signature), so it compares
|
|
132
|
+
runtimes from different builds by their loaded sections.
|
|
60
133
|
|
|
61
134
|
```bash
|
|
62
|
-
#
|
|
63
|
-
|
|
135
|
+
# Compile from source and emit into the committed prebuilt for this
|
|
136
|
+
# platform-arch (prebuilt/<platform>-<arch>-node137/, whatever Node version runs
|
|
137
|
+
# the build), stamping prebuilt/<triplet>/manifest.json from the built binary:
|
|
138
|
+
ADHDEV_GHOSTTY_VT_FORCE_COMPILE=1 ADHDEV_GHOSTTY_VT_EMIT_PREBUILT=1 npm run build -w oss/packages/ghostty-vt-node
|
|
139
|
+
|
|
140
|
+
# Or, after any build: copy build/Release into the committed triplet + stamp
|
|
141
|
+
node oss/packages/ghostty-vt-node/scripts/prebuilt-manifest.mjs emit
|
|
64
142
|
|
|
65
|
-
#
|
|
66
|
-
|
|
143
|
+
# Verify exactly that directory, then the whole set
|
|
144
|
+
node oss/packages/ghostty-vt-node/scripts/verify-prebuilt.mjs oss/packages/ghostty-vt-node/prebuilt/<triplet>
|
|
145
|
+
node oss/packages/ghostty-vt-node/scripts/check-prebuilts.mjs
|
|
67
146
|
```
|
|
68
147
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
148
|
+
With `ADHDEV_GHOSTTY_VT_FORCE_COMPILE=1` the build is a clean `cmake-js
|
|
149
|
+
rebuild` (fresh configure and clone); `build/` is removed with
|
|
150
|
+
`scripts/clean-dir.cjs` first, and the build stops if that fails. Without it, `npm run build` keeps an
|
|
151
|
+
existing local build or copies a usable prebuilt, or compiles incrementally
|
|
152
|
+
(`--plan` prints which it would do).
|
|
153
|
+
|
|
154
|
+
Binaries embed absolute build paths (the private zig caches live in the build
|
|
155
|
+
tree), so a prebuilt you commit must be built outside any home directory, from
|
|
156
|
+
a fresh clone of the commit being stamped. Copy the package to a neutral
|
|
157
|
+
directory and point `HOME` there for cmake-js' Node headers (shown for
|
|
158
|
+
macOS/Linux; on Windows use e.g. `C:\gvn` and `C:\gvn-home` for
|
|
159
|
+
`HOME`/`USERPROFILE`, with zig installed outside the user profile). The zig
|
|
160
|
+
caches need no setup: the build always uses fresh private ones. Do not set
|
|
161
|
+
`FETCHCONTENT_SOURCE_DIR_GHOSTTY`, `GHOSTTY_GIT_TAG`, `GHOSTTY_ZIG_BUILD_FLAGS`
|
|
162
|
+
or `ADHDEV_GHOSTTY_UNVERIFIED_DEV_BUILD`, and let nothing else write to these
|
|
163
|
+
directories during the build. On Windows, remove a previous `C:\gvn` with
|
|
164
|
+
`node oss\packages\ghostty-vt-node\scripts\clean-dir.cjs C:\gvn`: `rmdir /s`
|
|
165
|
+
and CMake cannot delete the `._.` files an earlier build's zig cache holds.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
rm -rf /tmp/gvn /tmp/gvn-home && mkdir -p /tmp/gvn/node_modules /tmp/gvn-home
|
|
169
|
+
rsync -a --exclude node_modules --exclude prebuilt --exclude build oss/packages/ghostty-vt-node/ /tmp/gvn/
|
|
170
|
+
cp -R oss/node_modules/node-addon-api /tmp/gvn/node_modules/ # or the package-local copy
|
|
171
|
+
(cd /tmp/gvn && HOME=/tmp/gvn-home node "$OLDPWD/oss/node_modules/cmake-js/bin/cmake-js" rebuild)
|
|
172
|
+
node oss/packages/ghostty-vt-node/scripts/probe-build-provenance.mjs /tmp/gvn # optional, expect 13/13
|
|
173
|
+
node oss/packages/ghostty-vt-node/scripts/prebuilt-manifest.mjs emit /tmp/gvn/build/Release
|
|
174
|
+
node oss/packages/ghostty-vt-node/scripts/check-prebuilts.mjs
|
|
175
|
+
```
|
|
72
176
|
|
|
73
177
|
- macOS arm64 and Linux x64 can be produced on the corresponding native runner.
|
|
74
178
|
- Windows x64 must be built on a Windows runner (MSVC; see the `if(MSVC)` block in
|
|
75
179
|
`CMakeLists.txt` for the required dynamic-CRT link flags).
|
|
76
180
|
|
|
181
|
+
### Release-safety gate (`scripts/check-prebuilts.mjs`)
|
|
182
|
+
|
|
183
|
+
CMake embeds a **binding source digest** (sha256 over `CMakeLists.txt`,
|
|
184
|
+
`toolchain.json` and `src/*`, LF-normalized; `scripts/binding-digest.mjs`
|
|
185
|
+
reproduces it) and the build provenance into the addon. Each shipped prebuilt
|
|
186
|
+
carries a `manifest.json` (schema 2) stamped from the binary itself plus file
|
|
187
|
+
hashes. The gate fails unless:
|
|
188
|
+
|
|
189
|
+
- (a) all three shipped triplets carry a manifest matching the current digest,
|
|
190
|
+
API level, ghostty pin and zig pin, with matching file hashes, stamped as a
|
|
191
|
+
release build from the pinned, clean ghostty checkout. A triplet built from
|
|
192
|
+
older sources, another zig, or an older manifest schema reports
|
|
193
|
+
**pending-rebuild**: after any bridge/addon/toolchain change the tree is not
|
|
194
|
+
shippable until every platform is rebuilt and re-stamped. Without loading
|
|
195
|
+
anything (release CI runs on Linux), the manifest's platform/arch must match
|
|
196
|
+
the directory, the platform's runtime set must be complete (darwin: addon +
|
|
197
|
+
`libghostty-vt.dylib` + `libghostty-vt.X.Y.Z.dylib`; linux: addon +
|
|
198
|
+
`libghostty-vt.so`, `.so.0`, `.so.0.X.Y`; win32: addon + `ghostty-vt.dll`),
|
|
199
|
+
and every file must be a thin Mach-O arm64 / ELF x86-64 (SONAME
|
|
200
|
+
`libghostty-vt.so.0`, NEEDed by the addon) / PE32+ x64 DLL
|
|
201
|
+
(`scripts/native-format.mjs`).
|
|
202
|
+
- (b) the host platform's committed prebuilt, loaded by path, embeds the
|
|
203
|
+
current digest and release provenance, and its viewport API behaves.
|
|
204
|
+
- (c) the native CI build compiles from source (`ADHDEV_GHOSTTY_VT_FORCE_COMPILE`)
|
|
205
|
+
as a clean `cmake-js rebuild` with the zig read from `toolchain.json` and no
|
|
206
|
+
restored zig cache (`setup-zig` `use-cache: false`, no `ZIG_*_CACHE_DIR`),
|
|
207
|
+
verifies + stamps the exact staged
|
|
208
|
+
artifact, and the release job checks every prebuilt it publishes
|
|
209
|
+
(`--all-present`).
|
|
210
|
+
- (d) no shipped native file (`.node`/`.dylib`/`.so*`/`.dll`) embeds a
|
|
211
|
+
home-directory path (`/Users/<name>/`, `/home/<name>/`, `X:\Users\<name>\`),
|
|
212
|
+
scanned as raw bytes in ASCII and UTF-16LE (`scripts/host-path-scan.mjs`; CI
|
|
213
|
+
accounts `runner`/`runneradmin` are allowed). Build prebuilts from a neutral
|
|
214
|
+
directory with a neutral zig cache (`ZIG_GLOBAL_CACHE_DIR`). Extra literals
|
|
215
|
+
(hostnames) can be denied from an untracked file named by
|
|
216
|
+
`ADHDEV_GHOSTTY_HOST_PATH_DENYLIST` or `.host-path-denylist` in this package.
|
|
217
|
+
`verify-prebuilt.mjs` runs the same scan on the directory it verifies.
|
|
218
|
+
|
|
77
219
|
## Skipping the native build
|
|
78
220
|
|
|
79
221
|
Set `ADHDEV_SKIP_GHOSTTY_VT_BUILD=1` to skip compilation when no prebuilt matches
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Pure candidate-ordering logic for the native binding loader (index.js).
|
|
3
|
+
// No native loading happens here so the selection rules are unit-testable with
|
|
4
|
+
// fake directory listings.
|
|
5
|
+
//
|
|
6
|
+
// Selection rule for prebuilt triplet directories of the host platform-arch:
|
|
7
|
+
// 1. highest manifest `apiLevel` first (a directory without a readable
|
|
8
|
+
// manifest counts as level 0 — never preferred over a manifest-bearing
|
|
9
|
+
// one, which is how a stale pre-API-2 binary loses to the current one even
|
|
10
|
+
// when its Node ABI number matches the running Node exactly),
|
|
11
|
+
// 2. then the exact running ABI (`...-node<abi>`),
|
|
12
|
+
// 3. then the higher ABI number, then name, for determinism.
|
|
13
|
+
// Foreign platform/arch directories (test-isolation fallback) come last.
|
|
14
|
+
|
|
15
|
+
function parseAbi(name) {
|
|
16
|
+
const match = /-node(\d+)$/.exec(name);
|
|
17
|
+
return match ? Number(match[1]) : -1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function orderPrebuiltTriplets({ names, platform, arch, abi, readApiLevel }) {
|
|
21
|
+
const prefix = `${platform}-${arch}-`;
|
|
22
|
+
const exact = `${platform}-${arch}-node${abi}`;
|
|
23
|
+
const level = (name) => {
|
|
24
|
+
const value = readApiLevel(name);
|
|
25
|
+
return Number.isFinite(value) ? value : 0;
|
|
26
|
+
};
|
|
27
|
+
const rank = (list) =>
|
|
28
|
+
list
|
|
29
|
+
.map((name) => ({ name, level: level(name), exact: name === exact ? 1 : 0, abi: parseAbi(name) }))
|
|
30
|
+
.sort((a, b) => b.level - a.level || b.exact - a.exact || b.abi - a.abi || a.name.localeCompare(b.name));
|
|
31
|
+
const same = names.filter((name) => name.startsWith(prefix));
|
|
32
|
+
const other = names.filter((name) => !name.startsWith(prefix));
|
|
33
|
+
return {
|
|
34
|
+
samePlatform: rank(same).map((entry) => entry.name),
|
|
35
|
+
bestApiLevel: same.length ? Math.max(...same.map(level)) : 0,
|
|
36
|
+
otherPlatform: other.slice().sort(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { orderPrebuiltTriplets };
|
package/index.d.ts
CHANGED
|
@@ -8,6 +8,97 @@ export type GhosttyPlainTextFormatOptions = {
|
|
|
8
8
|
trim?: boolean;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Coordinate space of a row index.
|
|
13
|
+
* - `active`: the rows x cols area the cursor moves in. `cursorRow` is in this
|
|
14
|
+
* space, and it does not move when the user scrolls through history.
|
|
15
|
+
* - `viewport`: the visible area. Equal to `active` unless the viewport is
|
|
16
|
+
* scrolled into history (`viewportScrollbackOffset > 0`).
|
|
17
|
+
*/
|
|
18
|
+
export type GhosttyCoordinateSpace = 'active' | 'viewport';
|
|
19
|
+
|
|
20
|
+
export type GhosttyCellWideKind = 'narrow' | 'wide' | 'spacer-tail' | 'spacer-head';
|
|
21
|
+
|
|
22
|
+
export type GhosttyCell = {
|
|
23
|
+
/** Physical cell column, 0-based. */
|
|
24
|
+
col: number;
|
|
25
|
+
/** The cell's full grapheme cluster as stored natively; '' for empty cells and spacers. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** 2 for a wide lead cell, 0 for spacers, 1 otherwise. */
|
|
28
|
+
width: 0 | 1 | 2;
|
|
29
|
+
wideKind: GhosttyCellWideKind;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type GhosttyRow = {
|
|
33
|
+
/** Row index, 0-based, in `coordinateSpace`. */
|
|
34
|
+
row: number;
|
|
35
|
+
coordinateSpace: GhosttyCoordinateSpace;
|
|
36
|
+
/**
|
|
37
|
+
* Row text. Empty cells before content read as U+0020; the trailing run of
|
|
38
|
+
* content-less cells is omitted; spacer cells contribute nothing. For
|
|
39
|
+
* readability only — `cells` is the authority for columns.
|
|
40
|
+
*/
|
|
41
|
+
text: string;
|
|
42
|
+
/** This row soft-wraps into the next physical row. */
|
|
43
|
+
wrapped: boolean;
|
|
44
|
+
/** This row continues a soft-wrap from the previous physical row. */
|
|
45
|
+
wrapContinuation: boolean;
|
|
46
|
+
/** Exactly `cols` entries, present when requested. */
|
|
47
|
+
cells?: GhosttyCell[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type GhosttyViewportOptions = {
|
|
51
|
+
/** Rows to include: none (default), the cursor's row, or all ACTIVE rows. */
|
|
52
|
+
includeRows?: 'none' | 'cursor' | 'all';
|
|
53
|
+
cells?: boolean;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type GhosttyScrollbar = {
|
|
57
|
+
/** Rows in the selected screen: retained history + active area. */
|
|
58
|
+
total: number;
|
|
59
|
+
/** Row of `total` at which the visible viewport starts. */
|
|
60
|
+
offset: number;
|
|
61
|
+
/** Visible rows. */
|
|
62
|
+
len: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** One native snapshot; every field describes the same terminal state. */
|
|
66
|
+
export type GhosttyViewport = {
|
|
67
|
+
/** Per-terminal mutation counter (write/resize/scrollViewport). */
|
|
68
|
+
revision: number;
|
|
69
|
+
/** Space of `cursorRow` and of every entry in `lines`. Always 'active'. */
|
|
70
|
+
coordinateSpace: 'active';
|
|
71
|
+
rows: number;
|
|
72
|
+
cols: number;
|
|
73
|
+
cursorRow: number;
|
|
74
|
+
cursorCol: number;
|
|
75
|
+
cursorPendingWrap: boolean;
|
|
76
|
+
/** DEC mode 25; says nothing about whether the cursor is in the viewport. */
|
|
77
|
+
cursorVisible: boolean;
|
|
78
|
+
activeScreen: 'primary' | 'alternate';
|
|
79
|
+
scrollbar: GhosttyScrollbar;
|
|
80
|
+
/** Retained history rows of the selected screen (scrollbar.total - scrollbar.len). */
|
|
81
|
+
scrollbackRows: number;
|
|
82
|
+
/** Screen row at which the ACTIVE area starts. */
|
|
83
|
+
activeOriginRow: number;
|
|
84
|
+
/** Screen row at which the VIEWPORT starts (scrollbar.offset). */
|
|
85
|
+
viewportOriginRow: number;
|
|
86
|
+
/** How many rows the viewport is scrolled up from the active area. */
|
|
87
|
+
viewportScrollbackOffset: number;
|
|
88
|
+
/** Cursor row in VIEWPORT space, or null when the cursor is scrolled out of view. */
|
|
89
|
+
cursorViewportRow: number | null;
|
|
90
|
+
/** ACTIVE rows: all of them, or just the cursor's row, per `includeRows`. */
|
|
91
|
+
lines?: GhosttyRow[];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type GhosttyReadRowOptions = {
|
|
95
|
+
cells?: boolean;
|
|
96
|
+
/** Defaults to 'active'. */
|
|
97
|
+
space?: GhosttyCoordinateSpace;
|
|
98
|
+
/** Throw ERR_GHOSTTY_STALE_REVISION unless the terminal is still at this revision. */
|
|
99
|
+
expectedRevision?: number;
|
|
100
|
+
};
|
|
101
|
+
|
|
11
102
|
export interface GhosttyTerminalHandle {
|
|
12
103
|
write(data: string | Uint8Array): void;
|
|
13
104
|
resize(cols: number, rows: number): void;
|
|
@@ -15,6 +106,22 @@ export interface GhosttyTerminalHandle {
|
|
|
15
106
|
formatVT(): string;
|
|
16
107
|
getCursorPosition(): { col: number; row: number };
|
|
17
108
|
dispose(): void;
|
|
109
|
+
// API level 2. Absent on older binaries: check `capabilities.viewport`.
|
|
110
|
+
getViewport?(options?: GhosttyViewportOptions): GhosttyViewport;
|
|
111
|
+
readRow?(row: number, options?: GhosttyReadRowOptions): GhosttyRow & { revision: number };
|
|
112
|
+
scrollViewport?(target: 'top' | 'bottom' | number): void;
|
|
18
113
|
}
|
|
19
114
|
|
|
115
|
+
export type GhosttyBindingInfo = {
|
|
116
|
+
/** 1 for binaries that predate getBindingInfo. */
|
|
117
|
+
apiLevel: number;
|
|
118
|
+
/** Binding source digest embedded at compile time (scripts/binding-digest.mjs). */
|
|
119
|
+
sourceDigest: string | null;
|
|
120
|
+
ghosttyRevision: string | null;
|
|
121
|
+
napiVersion: number | null;
|
|
122
|
+
};
|
|
123
|
+
|
|
20
124
|
export function createTerminal(options: GhosttyTerminalOptions): GhosttyTerminalHandle;
|
|
125
|
+
|
|
126
|
+
export const bindingInfo: GhosttyBindingInfo;
|
|
127
|
+
export const capabilities: Readonly<{ viewport: boolean }>;
|