chore: migrate to pure Go SQLite build
This commit is contained in:
@@ -76,27 +76,24 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
|
||||||
|
|
||||||
- name: Install Windows cross-compiler
|
- name: Install Windows packaging tools
|
||||||
if: matrix.os == 'windows' && matrix.cross
|
if: matrix.os == 'windows' && matrix.cross
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y gcc-mingw-w64-x86-64 gcc-mingw-w64-aarch64 zip
|
sudo apt-get install -y zip
|
||||||
|
|
||||||
- name: Build (non-Windows runners)
|
- name: Build (non-Windows runners)
|
||||||
if: matrix.runner != 'windows-latest'
|
if: matrix.runner != 'windows-latest'
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "0"
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.os }}" = "windows" ] && [ "${{ matrix.cross }}" = "true" ]; then
|
|
||||||
export CC=aarch64-w64-mingw32-gcc
|
|
||||||
fi
|
|
||||||
wails build -platform "${{ matrix.platform }}" -ldflags "-s -w"
|
wails build -platform "${{ matrix.platform }}" -ldflags "-s -w"
|
||||||
|
|
||||||
- name: Build (Windows runner)
|
- name: Build (Windows runner)
|
||||||
if: matrix.runner == 'windows-latest'
|
if: matrix.runner == 'windows-latest'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "0"
|
||||||
run: |
|
run: |
|
||||||
wails build -platform "${{ matrix.platform }}" -ldflags "-s -w"
|
wails build -platform "${{ matrix.platform }}" -ldflags "-s -w"
|
||||||
|
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ dev: deps
|
|||||||
|
|
||||||
# Production build
|
# Production build
|
||||||
build: deps
|
build: deps
|
||||||
CGO_CFLAGS="$${CGO_CFLAGS:--O2 -g0}" CGO_CXXFLAGS="$${CGO_CXXFLAGS:--O2 -g0}" $(WAILS) build -trimpath -ldflags "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
|
CGO_ENABLED=0 $(WAILS) build -trimpath -ldflags "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
|
||||||
|
|
||||||
# Production build with UPX compression
|
# Production build with UPX compression
|
||||||
build-compressed: deps upx-check
|
build-compressed: deps upx-check
|
||||||
CGO_CFLAGS="$${CGO_CFLAGS:--O2 -g0}" CGO_CXXFLAGS="$${CGO_CXXFLAGS:--O2 -g0}" $(WAILS) build -trimpath -upx $(if $(UPX_FLAGS),-upxflags "$(UPX_FLAGS)",) -ldflags "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
|
CGO_ENABLED=0 $(WAILS) build -trimpath -upx $(if $(UPX_FLAGS),-upxflags "$(UPX_FLAGS)",) -ldflags "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
test:
|
test:
|
||||||
|
|||||||
+2
-42
@@ -179,31 +179,6 @@ host_arch() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
pick_cc() {
|
|
||||||
local target_os="$1"
|
|
||||||
local target_arch="$2"
|
|
||||||
|
|
||||||
if [ "$target_os" = "windows" ]; then
|
|
||||||
if [ "$target_arch" = "amd64" ]; then
|
|
||||||
echo "x86_64-w64-mingw32-gcc"
|
|
||||||
else
|
|
||||||
echo "aarch64-w64-mingw32-gcc"
|
|
||||||
fi
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$target_os" = "linux" ]; then
|
|
||||||
if [ "$target_arch" = "amd64" ]; then
|
|
||||||
echo "x86_64-linux-gnu-gcc"
|
|
||||||
else
|
|
||||||
echo "aarch64-linux-gnu-gcc"
|
|
||||||
fi
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
build_target() {
|
build_target() {
|
||||||
local target_os="$1"
|
local target_os="$1"
|
||||||
local target_arch="$2"
|
local target_arch="$2"
|
||||||
@@ -211,7 +186,7 @@ build_target() {
|
|||||||
local host_arch_value="$4"
|
local host_arch_value="$4"
|
||||||
local version="$5"
|
local version="$5"
|
||||||
local platform="${target_os}/${target_arch}"
|
local platform="${target_os}/${target_arch}"
|
||||||
nexus_configure_release_cgo
|
nexus_configure_pure_go_build
|
||||||
local ldflags
|
local ldflags
|
||||||
ldflags="$(build_ldflags "${version}")"
|
ldflags="$(build_ldflags "${version}")"
|
||||||
local tags
|
local tags
|
||||||
@@ -235,29 +210,14 @@ build_target() {
|
|||||||
fi
|
fi
|
||||||
build_args+=("-ldflags" "$ldflags")
|
build_args+=("-ldflags" "$ldflags")
|
||||||
|
|
||||||
local cc=""
|
|
||||||
|
|
||||||
if [ "$target_os" = "linux" ] && ! linux_deps_ok; then
|
if [ "$target_os" = "linux" ] && ! linux_deps_ok; then
|
||||||
warn "Skipping ${platform} (missing Linux GTK/WebKit build dependencies)"
|
warn "Skipping ${platform} (missing Linux GTK/WebKit build dependencies)"
|
||||||
((SKIPPED_TARGETS += 1))
|
((SKIPPED_TARGETS += 1))
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$target_os" != "$host" ] || [ "$target_arch" != "$host_arch_value" ]; then
|
|
||||||
cc="$(pick_cc "$target_os" "$target_arch")"
|
|
||||||
if [ -n "$cc" ] && ! command -v "$cc" >/dev/null 2>&1; then
|
|
||||||
warn "Skipping ${platform} (missing C compiler: ${cc})"
|
|
||||||
((SKIPPED_TARGETS += 1))
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Building ${platform}…"
|
log "Building ${platform}…"
|
||||||
if [ -n "$cc" ]; then
|
CGO_ENABLED=0 wails_cmd build "${build_args[@]}"
|
||||||
CC="$cc" CGO_ENABLED=1 wails_cmd build "${build_args[@]}"
|
|
||||||
else
|
|
||||||
CGO_ENABLED=1 wails_cmd build "${build_args[@]}"
|
|
||||||
fi
|
|
||||||
((BUILT_TARGETS += 1))
|
((BUILT_TARGETS += 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ function Check-Deps {
|
|||||||
Write-Info "Node: $(& node --version)"
|
Write-Info "Node: $(& node --version)"
|
||||||
Write-Info "Wails: $(& $wailsPath version | Select-Object -First 1)"
|
Write-Info "Wails: $(& $wailsPath version | Select-Object -First 1)"
|
||||||
|
|
||||||
if (-not (Get-Command gcc -ErrorAction SilentlyContinue)) {
|
|
||||||
Write-Warn 'gcc not found. SQLite requires cgo + a C compiler (e.g. MSYS2 mingw-w64).'
|
|
||||||
}
|
|
||||||
|
|
||||||
return $wailsPath
|
return $wailsPath
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,9 +87,9 @@ function Build {
|
|||||||
$version = Get-BuildVersion
|
$version = Get-BuildVersion
|
||||||
$ldflags = Get-Ldflags $version
|
$ldflags = Get-Ldflags $version
|
||||||
|
|
||||||
$env:CGO_ENABLED = '1'
|
$env:CGO_ENABLED = '0'
|
||||||
if (-not $env:CGO_CFLAGS) { $env:CGO_CFLAGS = '-O2 -g0' }
|
Remove-Item Env:CGO_CFLAGS -ErrorAction SilentlyContinue
|
||||||
if (-not $env:CGO_CXXFLAGS) { $env:CGO_CXXFLAGS = '-O2 -g0' }
|
Remove-Item Env:CGO_CXXFLAGS -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
$buildArgs = @('build', '-trimpath')
|
$buildArgs = @('build', '-trimpath')
|
||||||
if (Test-BoolEnabled $env:NEXUS_COMPRESS) {
|
if (Test-BoolEnabled $env:NEXUS_COMPRESS) {
|
||||||
@@ -107,7 +103,7 @@ function Build {
|
|||||||
}
|
}
|
||||||
$buildArgs += @('-ldflags', $ldflags)
|
$buildArgs += @('-ldflags', $ldflags)
|
||||||
|
|
||||||
Write-Info 'Building production binary (CGO_ENABLED=1)...'
|
Write-Info 'Building production binary (CGO_ENABLED=0)...'
|
||||||
& $wailsPath @buildArgs
|
& $wailsPath @buildArgs
|
||||||
Write-Info 'Build complete: build\bin\'
|
Write-Info 'Build complete: build\bin\'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ build() {
|
|||||||
version="$(build_version)"
|
version="$(build_version)"
|
||||||
local tags
|
local tags
|
||||||
tags="$(build_tags)"
|
tags="$(build_tags)"
|
||||||
nexus_configure_release_cgo
|
nexus_configure_pure_go_build
|
||||||
local build_args=("-trimpath")
|
local build_args=("-trimpath")
|
||||||
if nexus_bool_enabled "${NEXUS_COMPRESS:-0}"; then
|
if nexus_bool_enabled "${NEXUS_COMPRESS:-0}"; then
|
||||||
if nexus_skips_upx_for_target "$(host_os)"; then
|
if nexus_skips_upx_for_target "$(host_os)"; then
|
||||||
|
|||||||
@@ -53,17 +53,9 @@ Build a packaged release for the current host platform:
|
|||||||
|
|
||||||
By default, `./release.sh` now builds only the current host OS/architecture. On this Ubuntu 24.04 `amd64` host that means `linux/amd64`.
|
By default, `./release.sh` now builds only the current host OS/architecture. On this Ubuntu 24.04 `amd64` host that means `linux/amd64`.
|
||||||
|
|
||||||
## Optional cross-compilers
|
## Optional cross-target builds
|
||||||
|
|
||||||
If you want to add extra Linux/Windows targets from Ubuntu, install the relevant cross-compiler packages first:
|
The release scripts disable CGO and use a pure-Go SQLite driver, so Linux and Windows cross-targets no longer need C cross-compilers for the application backend. Request explicit targets with:
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get install -y \
|
|
||||||
gcc-aarch64-linux-gnu \
|
|
||||||
gcc-mingw-w64-x86-64
|
|
||||||
```
|
|
||||||
|
|
||||||
Then request explicit targets:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
NEXUS_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh
|
NEXUS_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh
|
||||||
@@ -72,6 +64,5 @@ NEXUS_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh
|
|||||||
## Platform caveats
|
## Platform caveats
|
||||||
|
|
||||||
- macOS targets cannot be cross-built from Linux with Wails. Build those on macOS.
|
- macOS targets cannot be cross-built from Linux with Wails. Build those on macOS.
|
||||||
- `windows/arm64` is not covered by the default Ubuntu toolchain here.
|
- Cross-target support still depends on Wails and frontend packaging support for the requested platform.
|
||||||
- `linux/arm64` needs more than `gcc-aarch64-linux-gnu`; it also needs an `arm64` sysroot and target GTK/WebKit development libraries for CGO to link cleanly.
|
- Tested on Ubuntu 24.04 `amd64`: `linux/amd64` and `windows/amd64` build successfully.
|
||||||
- Tested on Ubuntu 24.04 `amd64`: `linux/amd64` and `windows/amd64` build successfully; `linux/arm64` does not in the default host setup above.
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ require (
|
|||||||
github.com/hajimehoshi/go-mp3 v0.3.4
|
github.com/hajimehoshi/go-mp3 v0.3.4
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.17.11
|
||||||
github.com/lrstanley/girc v1.1.1
|
github.com/lrstanley/girc v1.1.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.44
|
|
||||||
github.com/pkg/sftp v1.13.10
|
github.com/pkg/sftp v1.13.10
|
||||||
github.com/pquerna/otp v1.5.0
|
github.com/pquerna/otp v1.5.0
|
||||||
github.com/ulikunitz/xz v0.5.15
|
github.com/ulikunitz/xz v0.5.15
|
||||||
@@ -18,6 +17,7 @@ require (
|
|||||||
golang.org/x/crypto v0.52.0
|
golang.org/x/crypto v0.52.0
|
||||||
golang.org/x/net v0.55.0
|
golang.org/x/net v0.55.0
|
||||||
golang.org/x/text v0.37.0
|
golang.org/x/text v0.37.0
|
||||||
|
modernc.org/sqlite v1.53.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -25,6 +25,7 @@ require (
|
|||||||
github.com/bep/debounce v1.2.1 // indirect
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
github.com/boombuler/barcode v1.1.0 // indirect
|
github.com/boombuler/barcode v1.1.0 // indirect
|
||||||
github.com/danieljoos/wincred v1.2.3 // indirect
|
github.com/danieljoos/wincred v1.2.3 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||||
@@ -37,8 +38,10 @@ require (
|
|||||||
github.com/leaanthony/u v1.1.1 // indirect
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/samber/lo v1.53.0 // indirect
|
github.com/samber/lo v1.53.0 // indirect
|
||||||
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
github.com/tkrajina/go-reflector v0.5.8 // indirect
|
||||||
@@ -47,4 +50,7 @@ require (
|
|||||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
||||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
golang.org/x/sys v0.45.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
|
modernc.org/libc v1.73.4 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
@@ -23,6 +27,8 @@ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||||
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
|
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
|
||||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
|
||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
@@ -52,8 +58,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -64,6 +70,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@@ -93,9 +101,13 @@ github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPc
|
|||||||
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
||||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
|
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -110,5 +122,35 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||||
|
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c=
|
||||||
|
modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
|
modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=
|
||||||
|
modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE=
|
||||||
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc=
|
||||||
|
modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA=
|
||||||
|
modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||||
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
|
||||||
|
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "modernc.org/sqlite"
|
||||||
|
|
||||||
"github.com/andydixon/nexus/internal/models"
|
"github.com/andydixon/nexus/internal/models"
|
||||||
"github.com/andydixon/nexus/internal/storage/migrations"
|
"github.com/andydixon/nexus/internal/storage/migrations"
|
||||||
@@ -25,13 +25,17 @@ type Store struct {
|
|||||||
// NewStore opens (or creates) the SQLite database at dbPath, applies any
|
// NewStore opens (or creates) the SQLite database at dbPath, applies any
|
||||||
// pending migrations, and returns an initialised Store.
|
// pending migrations, and returns an initialised Store.
|
||||||
func NewStore(dbPath string) (*Store, error) {
|
func NewStore(dbPath string) (*Store, error) {
|
||||||
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
|
db, err := sql.Open("sqlite", dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("storage: failed to open database: %w", err)
|
return nil, fmt.Errorf("storage: failed to open database: %w", err)
|
||||||
}
|
}
|
||||||
if err := db.Ping(); err != nil {
|
if err := db.Ping(); err != nil {
|
||||||
return nil, fmt.Errorf("storage: database not reachable: %w", err)
|
return nil, fmt.Errorf("storage: database not reachable: %w", err)
|
||||||
}
|
}
|
||||||
|
if err := configureSQLite(db); err != nil {
|
||||||
|
_ = db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
s := &Store{db: db}
|
s := &Store{db: db}
|
||||||
if err := s.migrate(); err != nil {
|
if err := s.migrate(); err != nil {
|
||||||
@@ -40,6 +44,18 @@ func NewStore(dbPath string) (*Store, error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configureSQLite(db *sql.DB) error {
|
||||||
|
db.SetMaxOpenConns(1)
|
||||||
|
|
||||||
|
if _, err := db.Exec("PRAGMA busy_timeout = 5000"); err != nil {
|
||||||
|
return fmt.Errorf("storage: failed to set SQLite busy timeout: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := db.Exec("PRAGMA journal_mode = WAL"); err != nil {
|
||||||
|
return fmt.Errorf("storage: failed to enable SQLite WAL mode: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Close shuts down the database connection.
|
// Close shuts down the database connection.
|
||||||
func (s *Store) Close() error {
|
func (s *Store) Close() error {
|
||||||
return s.db.Close()
|
return s.db.Close()
|
||||||
|
|||||||
+4
-70
@@ -132,39 +132,6 @@ ensure_upx() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pick_cc() {
|
|
||||||
local target_os="$1"
|
|
||||||
local target_arch="$2"
|
|
||||||
|
|
||||||
if [ "$target_os" = "windows" ]; then
|
|
||||||
if command -v zig >/dev/null 2>&1; then
|
|
||||||
echo "zig:${target_arch}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
if [ "$target_arch" = "amd64" ]; then
|
|
||||||
echo "x86_64-w64-mingw32-gcc"
|
|
||||||
else
|
|
||||||
echo "aarch64-w64-mingw32-gcc"
|
|
||||||
fi
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$target_os" = "linux" ]; then
|
|
||||||
if command -v zig >/dev/null 2>&1; then
|
|
||||||
echo "zig:${target_arch}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
if [ "$target_arch" = "amd64" ]; then
|
|
||||||
echo "x86_64-linux-gnu-gcc"
|
|
||||||
else
|
|
||||||
echo "aarch64-linux-gnu-gcc"
|
|
||||||
fi
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
build_target() {
|
build_target() {
|
||||||
local target_os="$1"
|
local target_os="$1"
|
||||||
local target_arch="$2"
|
local target_arch="$2"
|
||||||
@@ -172,24 +139,6 @@ build_target() {
|
|||||||
local out_dir="$4"
|
local out_dir="$4"
|
||||||
|
|
||||||
local platform="${target_os}/${target_arch}"
|
local platform="${target_os}/${target_arch}"
|
||||||
local cc
|
|
||||||
cc="$(pick_cc "$target_os" "$target_arch")"
|
|
||||||
|
|
||||||
local zig_target=""
|
|
||||||
if [[ "$cc" == zig:* ]]; then
|
|
||||||
zig_target="${cc#zig:}"
|
|
||||||
if [ "$zig_target" = "amd64" ]; then
|
|
||||||
zig_target="x86_64"
|
|
||||||
elif [ "$zig_target" = "arm64" ]; then
|
|
||||||
zig_target="aarch64"
|
|
||||||
fi
|
|
||||||
cc="zig"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$cc" ] && ! command -v "$cc" >/dev/null 2>&1; then
|
|
||||||
warn "Skipping ${platform} (missing C compiler: ${cc})"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local output_base="NexusTerminal-${target_os}-${target_arch}"
|
local output_base="NexusTerminal-${target_os}-${target_arch}"
|
||||||
local output_path
|
local output_path
|
||||||
@@ -202,7 +151,7 @@ build_target() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
log "Building ${platform}..."
|
log "Building ${platform}..."
|
||||||
nexus_configure_release_cgo
|
nexus_configure_pure_go_build
|
||||||
local ldflags
|
local ldflags
|
||||||
ldflags="$(build_ldflags "$version")"
|
ldflags="$(build_ldflags "$version")"
|
||||||
local tags
|
local tags
|
||||||
@@ -220,24 +169,9 @@ build_target() {
|
|||||||
build_args+=("-tags" "$tags")
|
build_args+=("-tags" "$tags")
|
||||||
fi
|
fi
|
||||||
build_args+=("-ldflags" "$ldflags" "-o" "$output_path")
|
build_args+=("-ldflags" "$ldflags" "-o" "$output_path")
|
||||||
if [ -n "$cc" ]; then
|
if ! CGO_ENABLED=0 wails build "${build_args[@]}"; then
|
||||||
local cc_cmd="$cc"
|
warn "Build failed for ${platform}"
|
||||||
if [ -n "$zig_target" ]; then
|
return 1
|
||||||
if [ "$target_os" = "linux" ]; then
|
|
||||||
cc_cmd="zig cc -target ${zig_target}-linux-gnu"
|
|
||||||
elif [ "$target_os" = "windows" ]; then
|
|
||||||
cc_cmd="zig cc -target ${zig_target}-windows-gnu"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if ! CC="$cc_cmd" CGO_ENABLED=1 wails build "${build_args[@]}"; then
|
|
||||||
warn "Build failed for ${platform}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if ! CGO_ENABLED=1 wails build "${build_args[@]}"; then
|
|
||||||
warn "Build failed for ${platform}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local expected_path=""
|
local expected_path=""
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ nexus_skips_upx_for_target() {
|
|||||||
! nexus_bool_enabled "${NEXUS_FORCE_MACOS_UPX:-0}"
|
! nexus_bool_enabled "${NEXUS_FORCE_MACOS_UPX:-0}"
|
||||||
}
|
}
|
||||||
|
|
||||||
nexus_configure_release_cgo() {
|
nexus_configure_pure_go_build() {
|
||||||
export CGO_CFLAGS="${CGO_CFLAGS:--O2 -g0}"
|
export CGO_ENABLED=0
|
||||||
export CGO_CXXFLAGS="${CGO_CXXFLAGS:--O2 -g0}"
|
unset CGO_CFLAGS
|
||||||
|
unset CGO_CXXFLAGS
|
||||||
}
|
}
|
||||||
|
|
||||||
nexus_build_tags() {
|
nexus_build_tags() {
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
VSCODE_DIR="$ROOT_DIR/vscode"
|
|
||||||
OUT_DIR="$VSCODE_DIR/dist"
|
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
|
|
||||||
echo "[vscode] Building frontend assets"
|
|
||||||
if [ ! -d "$ROOT_DIR/frontend/node_modules" ]; then
|
|
||||||
npm --prefix "$ROOT_DIR/frontend" install
|
|
||||||
fi
|
|
||||||
npm --prefix "$ROOT_DIR/frontend" run build
|
|
||||||
|
|
||||||
echo "[vscode] Building Go bridge"
|
|
||||||
mkdir -p "$VSCODE_DIR/bin"
|
|
||||||
BRIDGE_NAME="nexus-vscode-bridge"
|
|
||||||
if [ "$(go env GOOS)" = "windows" ]; then
|
|
||||||
BRIDGE_NAME="nexus-vscode-bridge.exe"
|
|
||||||
fi
|
|
||||||
go build -trimpath -o "$VSCODE_DIR/bin/$BRIDGE_NAME" ./cmd/vscode-bridge
|
|
||||||
|
|
||||||
echo "[vscode] Staging extension assets"
|
|
||||||
for EXT in nexus leviathan; do
|
|
||||||
mkdir -p "$VSCODE_DIR/$EXT/bin"
|
|
||||||
cp "$VSCODE_DIR/bin/$BRIDGE_NAME" "$VSCODE_DIR/$EXT/bin/$BRIDGE_NAME"
|
|
||||||
done
|
|
||||||
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm.min.js" "$VSCODE_DIR/nexus/media/xterm.min.js"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm-addon-fit.min.js" "$VSCODE_DIR/nexus/media/xterm-addon-fit.min.js"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm-addon-unicode11.min.js" "$VSCODE_DIR/nexus/media/xterm-addon-unicode11.min.js"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/css/lib/xterm.css" "$VSCODE_DIR/nexus/media/xterm.css"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/novnc.min.js" "$VSCODE_DIR/nexus/media/novnc.min.js"
|
|
||||||
|
|
||||||
echo "[vscode] Packaging VSIX-compatible archives"
|
|
||||||
rm -rf "$OUT_DIR"
|
|
||||||
mkdir -p "$OUT_DIR"
|
|
||||||
|
|
||||||
package_extension() {
|
|
||||||
local ext="$1"
|
|
||||||
local out="$2"
|
|
||||||
if command -v vsce >/dev/null 2>&1; then
|
|
||||||
vsce package --no-dependencies --allow-missing-repository -o "$out"
|
|
||||||
else
|
|
||||||
npx --yes @vscode/vsce package --no-dependencies --allow-missing-repository -o "$out"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
for EXT in nexus leviathan; do
|
|
||||||
(
|
|
||||||
cd "$VSCODE_DIR/$EXT"
|
|
||||||
package_extension "$EXT" "$OUT_DIR/$EXT-0.1.0.vsix"
|
|
||||||
)
|
|
||||||
echo "[vscode] Wrote $OUT_DIR/$EXT-0.1.0.vsix"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[vscode] Build complete"
|
|
||||||
@@ -1,969 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const vscode = require('vscode');
|
|
||||||
const cp = require('child_process');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const fs = require('fs');
|
|
||||||
const http = require('http');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
function createCSPNonce() {
|
|
||||||
return crypto.randomBytes(16).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
const TOOLS = [
|
|
||||||
['Editor', 'hex', 'Hex Editor', 'Browse and patch bytes in the working copy.'],
|
|
||||||
['Triage', 'overview', 'Overview', 'Fast first-pass summary across hashes, strings, entropy and IOCs.'],
|
|
||||||
['Triage', 'hashes', 'Hashes', 'CRC32, MD5, SHA-1, SHA-256 and SHA-512.'],
|
|
||||||
['Triage', 'entropy', 'Entropy', 'Windowed entropy for packed or encrypted regions.'],
|
|
||||||
['Triage', 'strings', 'Strings', 'ASCII and UTF-16 string extraction.'],
|
|
||||||
['Triage', 'iocs', 'IOCs', 'URLs, domains, hashes, paths and indicators.'],
|
|
||||||
['Find', 'search', 'Search', 'Text, hex or regular-expression search.'],
|
|
||||||
['Find', 'multi-search', 'Multi-search', 'Batch text, hex and regex searches.'],
|
|
||||||
['Structure', 'parse', 'Structures', 'PE, ELF, Mach-O, ZIP, images, RIFF, ASN.1 and certificates.'],
|
|
||||||
['Structure', 'disasm', 'Disasm', 'Heuristic disassembly for common CPU families.'],
|
|
||||||
['Structure', 'pdf', 'PDF', 'Objects, streams, xrefs and suspicious PDF surface.'],
|
|
||||||
['Structure', 'squashfs', 'SquashFS', 'Superblock and directory metadata.'],
|
|
||||||
['Audio', 'spectrogram-encode', 'Spectrogram Encode', 'Convert images into WAV spectrogram payloads.'],
|
|
||||||
['Audio', 'spectrogram-view', 'Spectrogram View', 'Render audio files as spectrogram images.'],
|
|
||||||
['Compare', 'diff', 'Diff', 'Changed byte regions against a compare file.'],
|
|
||||||
['Recover', 'carve', 'Carve', 'Embedded file carving candidates.'],
|
|
||||||
['Recover', 'keys', 'Keys', 'PEM, DER, OpenSSH and PKCS#12 artefact discovery.'],
|
|
||||||
['Recover', 'timestamps', 'Timestamps', 'UNIX, FILETIME, WebKit and OLE timestamp heuristics.'],
|
|
||||||
['Recover', 'stego', 'Stego', 'Appended payloads and embedded trailer formats.'],
|
|
||||||
['Recover', 'metadata', 'Metadata', 'PNG, JPEG and GIF textual metadata extraction.'],
|
|
||||||
['Advanced', 'binsnoop', 'Binsnoop', 'Deep signature scanning through Binsnoop.'],
|
|
||||||
['Advanced', 'rules', 'Rules', 'YARA-like rule scans over the current document.'],
|
|
||||||
['Edit', 'patch-plan', 'Patch Plan', 'Validate patch scripts without modifying bytes.'],
|
|
||||||
['Edit', 'patch-apply', 'Patch Apply', 'Apply a patch script to the working copy.'],
|
|
||||||
['Edit', 'transform', 'Transforms', 'Hex, Base64, URL, XOR, inflate and cipher pipelines.'],
|
|
||||||
['Case', 'case', 'Case Export', 'Build a case archive payload from the working copy.']
|
|
||||||
].map(([group, id, name, desc]) => ({ group, id, name, desc }));
|
|
||||||
|
|
||||||
const DEFAULTS = {
|
|
||||||
search: { mode: 'text', query: 'MZ', max: 1000 },
|
|
||||||
'multi-search': { spec: 'text MZ\nhex 7f454c46\nregex https?://', max: 1000 },
|
|
||||||
parse: { kind: 'auto' },
|
|
||||||
disasm: { arch: 'x86_64', start: 0, length: 4096, max: 1000 },
|
|
||||||
entropy: { window: 4096, step: 4096 },
|
|
||||||
strings: { min: 4, max: 5000 },
|
|
||||||
iocs: { min: 5 },
|
|
||||||
carve: { max: 256 },
|
|
||||||
keys: { max: 200 },
|
|
||||||
timestamps: { max: 1000 },
|
|
||||||
rules: { source: 'rule mz { strings: $a = "MZ" condition: $a }', max: 1000 },
|
|
||||||
'patch-plan': { source: 'set 0x00 90 90' },
|
|
||||||
'patch-apply': { source: 'set 0x00 90 90' },
|
|
||||||
transform: { pipeline: 'hex.encode', start: 0, length: 1024 },
|
|
||||||
'spectrogram-view': { fftSize: 2048, height: 1024, maxWidth: 1400, aspectRatio: 1, minHz: 0, maxHz: 0, brightness: 1, contrast: 1 },
|
|
||||||
hex: { start: 0, bytes: 2048, width: 16 }
|
|
||||||
};
|
|
||||||
|
|
||||||
class Bridge {
|
|
||||||
constructor(context) {
|
|
||||||
this.context = context;
|
|
||||||
this.proc = null;
|
|
||||||
this.addr = null;
|
|
||||||
this.token = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
if (this.addr) return;
|
|
||||||
const exe = process.platform === 'win32' ? 'nexus-vscode-bridge.exe' : 'nexus-vscode-bridge';
|
|
||||||
const bridgePath = path.join(this.context.extensionPath, 'bin', exe);
|
|
||||||
if (!fs.existsSync(bridgePath)) {
|
|
||||||
throw new Error(`Bridge executable not found: ${bridgePath}. Run vscode/build.sh first.`);
|
|
||||||
}
|
|
||||||
// VSIX extraction does not always preserve the executable bit, so ensure it.
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
try { fs.chmodSync(bridgePath, 0o755); } catch (_) { /* best effort */ }
|
|
||||||
}
|
|
||||||
this.proc = cp.spawn(bridgePath, ['-listen', '127.0.0.1:0'], {
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
windowsHide: true
|
|
||||||
});
|
|
||||||
this.context.subscriptions.push({ dispose: () => this.stop() });
|
|
||||||
const firstLine = await new Promise((resolve, reject) => {
|
|
||||||
let buf = '';
|
|
||||||
const timer = setTimeout(() => reject(new Error('Timed out waiting for bridge startup')), 10000);
|
|
||||||
this.proc.stdout.on('data', chunk => {
|
|
||||||
buf += chunk.toString('utf8');
|
|
||||||
const idx = buf.indexOf('\n');
|
|
||||||
if (idx >= 0) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve(buf.slice(0, idx));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.proc.once('error', reject);
|
|
||||||
this.proc.once('exit', code => reject(new Error(`Bridge exited during startup (${code})`)));
|
|
||||||
});
|
|
||||||
const meta = JSON.parse(firstLine);
|
|
||||||
this.addr = meta.addr;
|
|
||||||
this.token = meta.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
if (this.proc && !this.proc.killed) this.proc.kill();
|
|
||||||
this.proc = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async rpc(method, args = []) {
|
|
||||||
await this.start();
|
|
||||||
const payload = Buffer.from(JSON.stringify({ method, args }));
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const req = http.request({
|
|
||||||
hostname: this.addr.split(':')[0],
|
|
||||||
port: Number(this.addr.split(':')[1]),
|
|
||||||
path: '/rpc',
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${this.token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Content-Length': payload.length
|
|
||||||
}
|
|
||||||
}, res => {
|
|
||||||
let data = '';
|
|
||||||
res.on('data', chunk => data += chunk);
|
|
||||||
res.on('end', () => {
|
|
||||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
||||||
reject(new Error(`Bridge HTTP ${res.statusCode}: ${data}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const decoded = JSON.parse(data || '{}');
|
|
||||||
if (decoded.error) reject(new Error(decoded.error));
|
|
||||||
else resolve(decoded.result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', reject);
|
|
||||||
req.write(payload);
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ToolsView {
|
|
||||||
constructor(context, bridge) {
|
|
||||||
this.context = context;
|
|
||||||
this.bridge = bridge;
|
|
||||||
this.view = null;
|
|
||||||
this.state = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveWebviewView(view) {
|
|
||||||
this.view = view;
|
|
||||||
view.webview.options = { enableScripts: true };
|
|
||||||
view.webview.html = this.html(view.webview);
|
|
||||||
view.webview.onDidReceiveMessage(async msg => {
|
|
||||||
try {
|
|
||||||
if (msg.type === 'load') await loadFile(this.bridge, false, this);
|
|
||||||
if (msg.type === 'compare') await loadFile(this.bridge, true, this);
|
|
||||||
if (msg.type === 'save') await saveFile(this.bridge, this);
|
|
||||||
if (msg.type === 'refresh') await this.refreshState();
|
|
||||||
if (msg.type === 'tool') await openTool(this.context, this.bridge, this, msg.tool);
|
|
||||||
} catch (err) {
|
|
||||||
this.postError(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.refreshState();
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshState() {
|
|
||||||
try {
|
|
||||||
this.state = await this.bridge.rpc('LeviathanState');
|
|
||||||
} catch (_) {
|
|
||||||
this.state = null;
|
|
||||||
}
|
|
||||||
this.postState();
|
|
||||||
}
|
|
||||||
|
|
||||||
postState() {
|
|
||||||
if (this.view) this.view.webview.postMessage({ type: 'state', state: this.state });
|
|
||||||
}
|
|
||||||
|
|
||||||
postError(err) {
|
|
||||||
if (this.view) this.view.webview.postMessage({ type: 'error', error: err.message || String(err) });
|
|
||||||
else vscode.window.showErrorMessage(err.message || String(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
html(webview) {
|
|
||||||
const nonce = createCSPNonce();
|
|
||||||
const toolsJson = JSON.stringify(TOOLS);
|
|
||||||
return `<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
font: var(--vscode-font-size) var(--vscode-font-family);
|
|
||||||
}
|
|
||||||
.shell { display: grid; gap: 10px; padding: 10px; }
|
|
||||||
.actions { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
|
|
||||||
button {
|
|
||||||
border: 1px solid var(--vscode-button-border, transparent);
|
|
||||||
background: var(--vscode-button-secondaryBackground);
|
|
||||||
color: var(--vscode-button-secondaryForeground);
|
|
||||||
padding: 5px 8px;
|
|
||||||
min-height: 26px;
|
|
||||||
font: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover { filter: brightness(1.08); }
|
|
||||||
button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); }
|
|
||||||
.state {
|
|
||||||
border: 1px solid var(--vscode-panel-border);
|
|
||||||
background: var(--vscode-editorWidget-background);
|
|
||||||
padding: 8px;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
.state strong { display: block; line-height: 18px; }
|
|
||||||
.state span { color: var(--vscode-descriptionForeground); font-size: 11px; }
|
|
||||||
.search {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid var(--vscode-input-border, transparent);
|
|
||||||
background: var(--vscode-input-background);
|
|
||||||
color: var(--vscode-input-foreground);
|
|
||||||
padding: 6px 8px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.search:focus { border-color: var(--vscode-focusBorder); }
|
|
||||||
.segments { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
||||||
.seg {
|
|
||||||
min-height: 23px;
|
|
||||||
padding: 3px 7px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
border: 1px solid var(--vscode-panel-border);
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
.seg.active {
|
|
||||||
background: var(--vscode-button-background);
|
|
||||||
color: var(--vscode-button-foreground);
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
.group {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 10px 0 5px;
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
font-size: 11px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: .04em;
|
|
||||||
}
|
|
||||||
.tool {
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
gap: 8px;
|
|
||||||
text-align: left;
|
|
||||||
border: 1px solid var(--vscode-panel-border);
|
|
||||||
background: var(--vscode-editor-background);
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
padding: 8px;
|
|
||||||
margin-bottom: 7px;
|
|
||||||
}
|
|
||||||
.tool:hover { border-color: var(--vscode-focusBorder); background: var(--vscode-list-hoverBackground); }
|
|
||||||
.mark {
|
|
||||||
width: 30px;
|
|
||||||
height: 28px;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
background: var(--vscode-badge-background);
|
|
||||||
color: var(--vscode-badge-foreground);
|
|
||||||
border: 1px solid var(--vscode-panel-border);
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.name { font-weight: 600; line-height: 17px; }
|
|
||||||
.desc { color: var(--vscode-descriptionForeground); font-size: 11px; line-height: 15px; }
|
|
||||||
.empty {
|
|
||||||
border: 1px dashed var(--vscode-panel-border);
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
text-align: center;
|
|
||||||
padding: 16px 10px;
|
|
||||||
}
|
|
||||||
.status { color: var(--vscode-descriptionForeground); font-size: 11px; min-height: 15px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="shell">
|
|
||||||
<div class="actions">
|
|
||||||
<button class="primary" id="load">Load File</button>
|
|
||||||
<button id="compare">Compare</button>
|
|
||||||
<button id="save">Save Copy</button>
|
|
||||||
<button id="refresh">Refresh</button>
|
|
||||||
</div>
|
|
||||||
<div class="state" id="state"><strong>No file loaded</strong><span>Load a binary or right-click a file in Explorer.</span></div>
|
|
||||||
<input id="search" class="search" placeholder="Search tools">
|
|
||||||
<div id="segments" class="segments"></div>
|
|
||||||
<div id="tools"></div>
|
|
||||||
<div id="status" class="status"></div>
|
|
||||||
</div>
|
|
||||||
<script nonce="${nonce}">
|
|
||||||
const vscode = acquireVsCodeApi();
|
|
||||||
const tools = ${toolsJson};
|
|
||||||
let state = null;
|
|
||||||
let filter = 'all';
|
|
||||||
const groups = ['all', ...Array.from(new Set(tools.map(t => t.group)))];
|
|
||||||
const search = document.getElementById('search');
|
|
||||||
document.getElementById('load').onclick = () => vscode.postMessage({ type: 'load' });
|
|
||||||
document.getElementById('compare').onclick = () => vscode.postMessage({ type: 'compare' });
|
|
||||||
document.getElementById('save').onclick = () => vscode.postMessage({ type: 'save' });
|
|
||||||
document.getElementById('refresh').onclick = () => vscode.postMessage({ type: 'refresh' });
|
|
||||||
function esc(value) {
|
|
||||||
return String(value == null ? '' : value).replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]));
|
|
||||||
}
|
|
||||||
function mark(tool) {
|
|
||||||
return tool.name.split(/\\s+/).map(part => part[0]).join('').slice(0, 3).toUpperCase();
|
|
||||||
}
|
|
||||||
function renderState() {
|
|
||||||
const el = document.getElementById('state');
|
|
||||||
if (!state || !state.name) {
|
|
||||||
el.innerHTML = '<strong>No file loaded</strong><span>Load a binary or right-click a file in Explorer.</span>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
el.innerHTML = '<strong>' + esc(state.name) + '</strong><span>' + Number(state.size || 0).toLocaleString() + ' bytes' + (state.dirty ? ' • modified' : '') + (state.compare ? ' • compare loaded' : '') + '</span>';
|
|
||||||
}
|
|
||||||
function renderSegments() {
|
|
||||||
const root = document.getElementById('segments');
|
|
||||||
root.textContent = '';
|
|
||||||
for (const group of groups) {
|
|
||||||
const count = group === 'all' ? tools.length : tools.filter(t => t.group === group).length;
|
|
||||||
const btn = document.createElement('button');
|
|
||||||
btn.className = 'seg' + (filter === group ? ' active' : '');
|
|
||||||
btn.textContent = group === 'all' ? 'All ' + count : group + ' ' + count;
|
|
||||||
btn.onclick = () => { filter = group; renderTools(); renderSegments(); };
|
|
||||||
root.appendChild(btn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function visibleTools() {
|
|
||||||
const q = search.value.trim().toLowerCase();
|
|
||||||
return tools.filter(tool => {
|
|
||||||
if (filter !== 'all' && tool.group !== filter) return false;
|
|
||||||
if (!q) return true;
|
|
||||||
return (tool.name + ' ' + tool.group + ' ' + tool.desc).toLowerCase().includes(q);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function renderTools() {
|
|
||||||
const root = document.getElementById('tools');
|
|
||||||
const visible = visibleTools();
|
|
||||||
if (!visible.length) {
|
|
||||||
root.innerHTML = '<div class="empty">No matching tools</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const byGroup = new Map();
|
|
||||||
for (const tool of visible) {
|
|
||||||
if (!byGroup.has(tool.group)) byGroup.set(tool.group, []);
|
|
||||||
byGroup.get(tool.group).push(tool);
|
|
||||||
}
|
|
||||||
root.innerHTML = [...byGroup.entries()].map(([group, items]) =>
|
|
||||||
'<div class="group"><span>' + esc(group) + '</span><span>' + items.length + '</span></div>' +
|
|
||||||
items.map(tool => '<button class="tool" data-tool="' + esc(tool.id) + '"><span class="mark">' + esc(mark(tool)) + '</span><span><span class="name">' + esc(tool.name) + '</span><br><span class="desc">' + esc(tool.desc) + '</span></span></button>').join('')
|
|
||||||
).join('');
|
|
||||||
}
|
|
||||||
document.getElementById('tools').addEventListener('click', event => {
|
|
||||||
const btn = event.target.closest('[data-tool]');
|
|
||||||
if (btn) vscode.postMessage({ type: 'tool', tool: btn.dataset.tool });
|
|
||||||
});
|
|
||||||
search.addEventListener('input', renderTools);
|
|
||||||
renderSegments();
|
|
||||||
renderTools();
|
|
||||||
window.addEventListener('message', event => {
|
|
||||||
const msg = event.data || {};
|
|
||||||
if (msg.type === 'state') {
|
|
||||||
state = msg.state;
|
|
||||||
renderState();
|
|
||||||
}
|
|
||||||
if (msg.type === 'error') document.getElementById('status').textContent = msg.error || '';
|
|
||||||
});
|
|
||||||
vscode.postMessage({ type: 'refresh' });
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ToolPanel {
|
|
||||||
constructor(context, bridge, tool, options = {}) {
|
|
||||||
this.context = context;
|
|
||||||
this.bridge = bridge;
|
|
||||||
this.tool = tool;
|
|
||||||
this.fileState = options.fileState || null;
|
|
||||||
const meta = TOOLS.find(t => t.id === tool) || { name: tool };
|
|
||||||
const title = this.fileState?.name ? `${meta.name}: ${this.fileState.name}` : meta.name;
|
|
||||||
this.panel = vscode.window.createWebviewPanel('leviathanTool', title, vscode.ViewColumn.Active, { enableScripts: true, retainContextWhenHidden: true });
|
|
||||||
this.panel.webview.html = this.tool === 'hex' ? this.hexHtml(meta) : this.html(meta);
|
|
||||||
this.panel.webview.onDidReceiveMessage(msg => {
|
|
||||||
if (msg.type === 'run') this.run(msg.params);
|
|
||||||
if (msg.type === 'hexLoad') this.loadHex(msg);
|
|
||||||
if (msg.type === 'hexSet') this.setHexByte(msg);
|
|
||||||
if (msg.type === 'hexSearch') this.searchHex(msg);
|
|
||||||
if (msg.type === 'undo') this.stateAction('LeviathanUndo', msg);
|
|
||||||
if (msg.type === 'redo') this.stateAction('LeviathanRedo', msg);
|
|
||||||
if (msg.type === 'revert') this.stateAction('LeviathanRevert', msg);
|
|
||||||
});
|
|
||||||
this.panel.webview.postMessage({ type: 'fileState', state: this.fileState });
|
|
||||||
this.refreshState();
|
|
||||||
if (options.autoRun) {
|
|
||||||
if (this.tool === 'hex') {
|
|
||||||
setTimeout(() => this.loadHex(DEFAULTS.hex), 150);
|
|
||||||
} else {
|
|
||||||
setTimeout(() => this.run(JSON.stringify(DEFAULTS[this.tool] || {})), 150);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshState() {
|
|
||||||
try {
|
|
||||||
const state = await this.bridge.rpc('LeviathanState');
|
|
||||||
this.fileState = state;
|
|
||||||
this.panel.webview.postMessage({ type: 'fileState', state });
|
|
||||||
} catch (err) {
|
|
||||||
this.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async run(paramsText) {
|
|
||||||
try {
|
|
||||||
const params = JSON.parse(paramsText || '{}');
|
|
||||||
const result = await this.bridge.rpc('LeviathanRunTool', [this.tool, params]);
|
|
||||||
this.panel.webview.postMessage({ type: 'result', result });
|
|
||||||
} catch (err) {
|
|
||||||
this.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error(err) {
|
|
||||||
this.panel.webview.postMessage({ type: 'error', error: err.message || String(err) });
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadHex(params = {}) {
|
|
||||||
try {
|
|
||||||
const start = Number(params.start || 0);
|
|
||||||
const bytes = Number(params.bytes || 2048);
|
|
||||||
const width = Number(params.width || 16);
|
|
||||||
const result = await this.bridge.rpc('LeviathanRunTool', ['hex', { start, bytes, width }]);
|
|
||||||
this.panel.webview.postMessage({ type: 'hexRows', rows: result.rows || [], start, bytes, width });
|
|
||||||
await this.refreshState();
|
|
||||||
} catch (err) {
|
|
||||||
this.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setHexByte(msg) {
|
|
||||||
try {
|
|
||||||
const offset = Number(msg.offset);
|
|
||||||
const value = String(msg.value || '').trim();
|
|
||||||
const payload = await this.bridge.rpc('LeviathanSetByte', [offset, value]);
|
|
||||||
if (payload && payload.state) {
|
|
||||||
this.fileState = payload.state;
|
|
||||||
this.panel.webview.postMessage({ type: 'fileState', state: payload.state });
|
|
||||||
}
|
|
||||||
await this.loadHex({ start: msg.start, bytes: msg.bytes, width: msg.width });
|
|
||||||
} catch (err) {
|
|
||||||
this.panel.webview.postMessage({ type: 'hexEditError', offset: msg.offset, error: err.message || String(err) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async searchHex(msg) {
|
|
||||||
try {
|
|
||||||
const mode = msg.mode === 'hex' ? 'hex' : 'text';
|
|
||||||
const query = String(msg.query || '');
|
|
||||||
const result = await this.bridge.rpc('LeviathanRunTool', ['search', { mode, query, max: 2000 }]);
|
|
||||||
this.panel.webview.postMessage({ type: 'hexSearchResults', hits: result.hits || [], mode, query });
|
|
||||||
} catch (err) {
|
|
||||||
this.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async stateAction(method, msg = {}) {
|
|
||||||
try {
|
|
||||||
const state = await this.bridge.rpc(method);
|
|
||||||
this.panel.webview.postMessage({ type: 'state', state });
|
|
||||||
this.panel.webview.postMessage({ type: 'fileState', state });
|
|
||||||
if (this.tool === 'hex') {
|
|
||||||
await this.loadHex(msg.start === undefined ? DEFAULTS.hex : msg);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
this.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hexHtml(meta) {
|
|
||||||
const nonce = createCSPNonce();
|
|
||||||
const initialFileState = JSON.stringify(this.fileState || null);
|
|
||||||
const name = escapeHtml(meta.name);
|
|
||||||
return `<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
background: var(--vscode-editor-background);
|
|
||||||
font: var(--vscode-font-size) var(--vscode-font-family);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
header {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 9px 10px;
|
|
||||||
border-bottom: 1px solid var(--vscode-panel-border);
|
|
||||||
}
|
|
||||||
h1 { margin: 0; font-size: 15px; line-height: 19px; }
|
|
||||||
.file-state { color: var(--vscode-descriptionForeground); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
||||||
.toolbar {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto auto auto auto 1fr auto auto;
|
|
||||||
gap: 7px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 1px solid var(--vscode-panel-border);
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
}
|
|
||||||
.searchbar {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 1fr auto auto auto;
|
|
||||||
gap: 7px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 1px solid var(--vscode-panel-border);
|
|
||||||
background: var(--vscode-sideBar-background);
|
|
||||||
}
|
|
||||||
input, select {
|
|
||||||
min-width: 0;
|
|
||||||
color: var(--vscode-input-foreground);
|
|
||||||
background: var(--vscode-input-background);
|
|
||||||
border: 1px solid var(--vscode-input-border, transparent);
|
|
||||||
padding: 5px 7px;
|
|
||||||
font: 12px Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
input:focus, select:focus { outline: 1px solid var(--vscode-focusBorder); }
|
|
||||||
button {
|
|
||||||
border: 1px solid var(--vscode-button-border, transparent);
|
|
||||||
background: var(--vscode-button-secondaryBackground);
|
|
||||||
color: var(--vscode-button-secondaryForeground);
|
|
||||||
padding: 5px 9px;
|
|
||||||
min-height: 26px;
|
|
||||||
font: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); }
|
|
||||||
.seg { display: inline-flex; border: 1px solid var(--vscode-panel-border); }
|
|
||||||
.seg button { border: 0; border-right: 1px solid var(--vscode-panel-border); background: transparent; }
|
|
||||||
.seg button:last-child { border-right: 0; }
|
|
||||||
.seg button.active { background: var(--vscode-button-background); color: var(--vscode-button-foreground); }
|
|
||||||
main { height: calc(100vh - 132px); overflow: auto; }
|
|
||||||
table { width: 100%; border-collapse: collapse; font: 12px Menlo, Monaco, Consolas, "Courier New", monospace; }
|
|
||||||
th, td { border-bottom: 1px solid var(--vscode-panel-border); padding: 3px 6px; vertical-align: top; }
|
|
||||||
th { position: sticky; top: 0; background: var(--vscode-editor-background); z-index: 1; text-align: left; color: var(--vscode-descriptionForeground); }
|
|
||||||
.offset { width: 88px; color: var(--vscode-descriptionForeground); user-select: text; }
|
|
||||||
.hex-cell {
|
|
||||||
width: 2.35ch;
|
|
||||||
margin-right: .2ch;
|
|
||||||
padding: 1px 2px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font: 12px Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
||||||
}
|
|
||||||
.hex-cell:focus { border-color: var(--vscode-focusBorder); background: var(--vscode-input-background); outline: none; }
|
|
||||||
.hex-cell.dirty { color: var(--vscode-terminal-ansiYellow); }
|
|
||||||
.hex-cell.hit, .ascii-char.hit { background: var(--vscode-editor-findMatchHighlightBackground); }
|
|
||||||
.ascii {
|
|
||||||
white-space: pre;
|
|
||||||
color: var(--vscode-descriptionForeground);
|
|
||||||
letter-spacing: .08em;
|
|
||||||
min-width: 34ch;
|
|
||||||
}
|
|
||||||
.ascii-char { display: inline-block; min-width: 1ch; }
|
|
||||||
.status { color: var(--vscode-descriptionForeground); font-size: 12px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<div><h1>${name}</h1><div id="fileState" class="file-state">Checking loaded file...</div></div>
|
|
||||||
<div><button id="undo">Undo</button><button id="redo">Redo</button><button id="revert">Revert</button></div>
|
|
||||||
</header>
|
|
||||||
<section class="toolbar">
|
|
||||||
<label>Offset <input id="start" value="0" size="10"></label>
|
|
||||||
<label>Bytes <input id="bytes" value="2048" size="8"></label>
|
|
||||||
<span class="seg"><button id="width16" class="active">16</button><button id="width32">32</button></span>
|
|
||||||
<button id="prev">Prev</button>
|
|
||||||
<button id="next">Next</button>
|
|
||||||
<button id="load" class="primary">Load</button>
|
|
||||||
<span id="status" class="status"></span>
|
|
||||||
</section>
|
|
||||||
<section class="searchbar">
|
|
||||||
<select id="searchMode"><option value="hex">Hex</option><option value="ascii">ASCII</option></select>
|
|
||||||
<input id="searchQuery" placeholder="Search hex bytes or ASCII text">
|
|
||||||
<button id="search">Search</button>
|
|
||||||
<button id="searchPrev">Prev Hit</button>
|
|
||||||
<button id="searchNext">Next Hit</button>
|
|
||||||
</section>
|
|
||||||
<main>
|
|
||||||
<table>
|
|
||||||
<thead><tr><th class="offset">Offset</th><th>Hex</th><th>ASCII</th></tr></thead>
|
|
||||||
<tbody id="rows"></tbody>
|
|
||||||
</table>
|
|
||||||
</main>
|
|
||||||
<script nonce="${nonce}">
|
|
||||||
const vscode = acquireVsCodeApi();
|
|
||||||
const initialFileState = ${initialFileState};
|
|
||||||
let width = 16;
|
|
||||||
let rows = [];
|
|
||||||
let hits = [];
|
|
||||||
let hitIndex = -1;
|
|
||||||
const changed = new Map();
|
|
||||||
function esc(value) {
|
|
||||||
return String(value == null ? '' : value).replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]));
|
|
||||||
}
|
|
||||||
function parseNum(value, fallback) {
|
|
||||||
const text = String(value || '').trim();
|
|
||||||
if (!text) return fallback;
|
|
||||||
const n = Number(text.startsWith('0x') || text.startsWith('0X') ? parseInt(text, 16) : parseInt(text, 10));
|
|
||||||
return Number.isFinite(n) ? n : fallback;
|
|
||||||
}
|
|
||||||
function printable(hex) {
|
|
||||||
const n = parseInt(hex, 16);
|
|
||||||
return n >= 32 && n < 127 ? String.fromCharCode(n) : '.';
|
|
||||||
}
|
|
||||||
function currentParams() {
|
|
||||||
return {
|
|
||||||
start: parseNum(document.getElementById('start').value, 0),
|
|
||||||
bytes: parseNum(document.getElementById('bytes').value, 2048),
|
|
||||||
width
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function renderFileState(s) {
|
|
||||||
document.getElementById('fileState').textContent = s && s.name ? s.name + ' • ' + Number(s.size || 0).toLocaleString() + ' bytes' + (s.dirty ? ' • modified' : '') : 'No file loaded';
|
|
||||||
}
|
|
||||||
function hitOffsets() {
|
|
||||||
const set = new Set();
|
|
||||||
for (const hit of hits) {
|
|
||||||
const start = Number(hit.offset || hit.start || 0);
|
|
||||||
const length = Math.max(1, Number(hit.length || 1));
|
|
||||||
for (let i = 0; i < length; i++) set.add(start + i);
|
|
||||||
}
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
function renderRows() {
|
|
||||||
const hitSet = hitOffsets();
|
|
||||||
const body = document.getElementById('rows');
|
|
||||||
body.textContent = '';
|
|
||||||
for (const row of rows) {
|
|
||||||
const tr = document.createElement('tr');
|
|
||||||
const off = Number(row.offset || 0);
|
|
||||||
const bytes = Array.isArray(row.bytes) ? row.bytes : String(row.hex || '').split(/\\s+/).filter(Boolean);
|
|
||||||
const hexTd = document.createElement('td');
|
|
||||||
const asciiTd = document.createElement('td');
|
|
||||||
asciiTd.className = 'ascii';
|
|
||||||
bytes.forEach((byte, idx) => {
|
|
||||||
const absolute = off + idx;
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.className = 'hex-cell' + (changed.has(absolute) ? ' dirty' : '') + (hitSet.has(absolute) ? ' hit' : '');
|
|
||||||
input.maxLength = 2;
|
|
||||||
input.value = String(changed.get(absolute) || byte).toUpperCase();
|
|
||||||
input.dataset.offset = String(absolute);
|
|
||||||
input.addEventListener('input', () => {
|
|
||||||
input.value = input.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 2).toUpperCase();
|
|
||||||
updateAsciiFromInputs(tr);
|
|
||||||
});
|
|
||||||
input.addEventListener('change', () => commitByte(input));
|
|
||||||
input.addEventListener('keydown', event => {
|
|
||||||
if (event.key === 'Enter') { event.preventDefault(); input.blur(); }
|
|
||||||
});
|
|
||||||
hexTd.appendChild(input);
|
|
||||||
});
|
|
||||||
tr.innerHTML = '<td class="offset">0x' + off.toString(16).padStart(8, '0') + '</td>';
|
|
||||||
tr.appendChild(hexTd);
|
|
||||||
tr.appendChild(asciiTd);
|
|
||||||
body.appendChild(tr);
|
|
||||||
updateAsciiFromInputs(tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function updateAsciiFromInputs(tr) {
|
|
||||||
const asciiTd = tr.querySelector('.ascii');
|
|
||||||
asciiTd.textContent = '';
|
|
||||||
for (const input of tr.querySelectorAll('.hex-cell')) {
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.className = 'ascii-char' + (input.classList.contains('hit') ? ' hit' : '');
|
|
||||||
span.textContent = /^[0-9A-Fa-f]{2}$/.test(input.value) ? printable(input.value) : '.';
|
|
||||||
asciiTd.appendChild(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function commitByte(input) {
|
|
||||||
if (!/^[0-9A-Fa-f]{2}$/.test(input.value)) {
|
|
||||||
document.getElementById('status').textContent = 'Byte must be two hex digits';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const offset = Number(input.dataset.offset);
|
|
||||||
changed.set(offset, input.value.toUpperCase());
|
|
||||||
input.classList.add('dirty');
|
|
||||||
vscode.postMessage({ type: 'hexSet', offset, value: input.value, ...currentParams() });
|
|
||||||
}
|
|
||||||
function load() {
|
|
||||||
document.getElementById('status').textContent = 'Loading...';
|
|
||||||
vscode.postMessage({ type: 'hexLoad', ...currentParams() });
|
|
||||||
}
|
|
||||||
function gotoHit(delta) {
|
|
||||||
if (!hits.length) return;
|
|
||||||
hitIndex = (hitIndex + delta + hits.length) % hits.length;
|
|
||||||
const offset = Number(hits[hitIndex].offset || hits[hitIndex].start || 0);
|
|
||||||
const aligned = Math.max(0, Math.floor(offset / width) * width);
|
|
||||||
document.getElementById('start').value = String(aligned);
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
document.getElementById('width16').onclick = () => { width = 16; document.getElementById('width16').classList.add('active'); document.getElementById('width32').classList.remove('active'); load(); };
|
|
||||||
document.getElementById('width32').onclick = () => { width = 32; document.getElementById('width32').classList.add('active'); document.getElementById('width16').classList.remove('active'); load(); };
|
|
||||||
document.getElementById('load').onclick = load;
|
|
||||||
document.getElementById('prev').onclick = () => { const p = currentParams(); document.getElementById('start').value = String(Math.max(0, p.start - p.bytes)); load(); };
|
|
||||||
document.getElementById('next').onclick = () => { const p = currentParams(); document.getElementById('start').value = String(p.start + p.bytes); load(); };
|
|
||||||
document.getElementById('search').onclick = () => {
|
|
||||||
const mode = document.getElementById('searchMode').value === 'hex' ? 'hex' : 'ascii';
|
|
||||||
vscode.postMessage({ type: 'hexSearch', mode, query: document.getElementById('searchQuery').value });
|
|
||||||
};
|
|
||||||
document.getElementById('searchPrev').onclick = () => gotoHit(-1);
|
|
||||||
document.getElementById('searchNext').onclick = () => gotoHit(1);
|
|
||||||
document.getElementById('undo').onclick = () => vscode.postMessage({ type: 'undo', ...currentParams() });
|
|
||||||
document.getElementById('redo').onclick = () => vscode.postMessage({ type: 'redo', ...currentParams() });
|
|
||||||
document.getElementById('revert').onclick = () => vscode.postMessage({ type: 'revert', ...currentParams() });
|
|
||||||
window.addEventListener('message', event => {
|
|
||||||
const msg = event.data || {};
|
|
||||||
if (msg.type === 'fileState') renderFileState(msg.state);
|
|
||||||
if (msg.type === 'hexRows') {
|
|
||||||
rows = msg.rows || [];
|
|
||||||
width = Number(msg.width || width);
|
|
||||||
renderRows();
|
|
||||||
document.getElementById('status').textContent = rows.length ? rows.length + ' rows' : 'No bytes in range';
|
|
||||||
}
|
|
||||||
if (msg.type === 'hexSearchResults') {
|
|
||||||
hits = msg.hits || [];
|
|
||||||
hitIndex = hits.length ? 0 : -1;
|
|
||||||
document.getElementById('status').textContent = hits.length + ' hit' + (hits.length === 1 ? '' : 's');
|
|
||||||
if (hits.length) {
|
|
||||||
const offset = Number(hits[0].offset || hits[0].start || 0);
|
|
||||||
document.getElementById('start').value = String(Math.max(0, Math.floor(offset / width) * width));
|
|
||||||
load();
|
|
||||||
} else {
|
|
||||||
renderRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.type === 'hexEditError') document.getElementById('status').textContent = msg.error || 'Edit failed';
|
|
||||||
if (msg.type === 'state') renderFileState(msg.state);
|
|
||||||
if (msg.type === 'error') document.getElementById('status').textContent = msg.error || '';
|
|
||||||
});
|
|
||||||
renderFileState(initialFileState);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html(meta) {
|
|
||||||
const nonce = createCSPNonce();
|
|
||||||
const defaults = JSON.stringify(DEFAULTS[this.tool] || {});
|
|
||||||
const initialFileState = JSON.stringify(this.fileState || null);
|
|
||||||
const name = escapeHtml(meta.name);
|
|
||||||
return `<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
|
||||||
<style>
|
|
||||||
body { margin: 0; color: var(--vscode-foreground); background: var(--vscode-editor-background); font: var(--vscode-font-size) var(--vscode-font-family); }
|
|
||||||
header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--vscode-panel-border); }
|
|
||||||
h1 { margin: 0; font-size: 15px; }
|
|
||||||
.title-wrap { min-width: 0; }
|
|
||||||
.file-state { color: var(--vscode-descriptionForeground); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
||||||
header button, .actions button { border: 1px solid var(--vscode-button-border, transparent); background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); }
|
|
||||||
header button:hover, .actions button:hover { background: var(--vscode-button-secondaryHoverBackground, var(--vscode-button-secondaryBackground)); }
|
|
||||||
main { display: grid; grid-template-columns: minmax(260px, 360px) 1fr; height: calc(100vh - 58px); }
|
|
||||||
.left { border-right: 1px solid var(--vscode-panel-border); display: flex; flex-direction: column; min-height: 0; min-width: 0; }
|
|
||||||
.params-head { padding: 12px 12px 6px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--vscode-descriptionForeground); }
|
|
||||||
textarea { flex: 1; margin: 0 12px; resize: none; color: var(--vscode-input-foreground); background: var(--vscode-input-background); border: 1px solid var(--vscode-input-border); padding: 8px; box-sizing: border-box; font-family: var(--vscode-editor-font-family, monospace); font-size: 12px; }
|
|
||||||
textarea:focus { outline: none; border-color: var(--vscode-focusBorder); }
|
|
||||||
.actions { display: flex; gap: 6px; padding: 10px 12px; }
|
|
||||||
button { padding: 5px 10px; cursor: pointer; }
|
|
||||||
button.primary { background: var(--vscode-button-background); color: var(--vscode-button-foreground); border-color: var(--vscode-button-border, transparent); }
|
|
||||||
button.primary:hover { background: var(--vscode-button-hoverBackground, var(--vscode-button-background)); }
|
|
||||||
#out { margin: 0; padding: 12px; overflow: auto; height: calc(100% - 24px); box-sizing: border-box; }
|
|
||||||
.out-empty { color: var(--vscode-descriptionForeground); }
|
|
||||||
table { border-collapse: collapse; width: 100%; margin: 0 0 12px; }
|
|
||||||
th, td { border: 1px solid var(--vscode-panel-border); padding: 4px 6px; text-align: left; vertical-align: top; font-family: var(--vscode-editor-font-family); font-size: 12px; }
|
|
||||||
th { background: var(--vscode-editorWidget-background); position: sticky; top: 0; }
|
|
||||||
pre { white-space: pre-wrap; word-break: break-word; }
|
|
||||||
img { max-width: 100%; height: auto; border: 1px solid var(--vscode-panel-border); }
|
|
||||||
h2 { font-size: 14px; margin: 12px 0 6px; }
|
|
||||||
.hex-table td, .hex-table th { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; }
|
|
||||||
.hex-cell { display: inline-block; min-width: 2.2ch; margin-right: .35ch; }
|
|
||||||
.ascii { color: var(--vscode-descriptionForeground); }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<div class="title-wrap"><h1>${name}</h1><div id="fileState" class="file-state">Checking loaded file...</div></div>
|
|
||||||
<div><button id="undo">Undo</button><button id="redo">Redo</button><button id="revert">Revert</button></div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<section class="left">
|
|
||||||
<div class="params-head">Parameters (JSON)</div>
|
|
||||||
<textarea id="params" spellcheck="false">${escapeHtml(JSON.stringify(JSON.parse(defaults), null, 2))}</textarea>
|
|
||||||
<div class="actions"><button id="run" class="primary">Run</button></div>
|
|
||||||
</section>
|
|
||||||
<section id="out"><span class="out-empty">Adjust the parameters and select Run to see results.</span></section>
|
|
||||||
</main>
|
|
||||||
<script nonce="${nonce}">
|
|
||||||
const vscode = acquireVsCodeApi();
|
|
||||||
const initialFileState = ${initialFileState};
|
|
||||||
const runTool = () => vscode.postMessage({ type: 'run', params: document.getElementById('params').value });
|
|
||||||
document.getElementById('run').onclick = runTool;
|
|
||||||
document.getElementById('params').addEventListener('keydown', event => {
|
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { event.preventDefault(); runTool(); }
|
|
||||||
});
|
|
||||||
document.getElementById('undo').onclick = () => vscode.postMessage({ type: 'undo' });
|
|
||||||
document.getElementById('redo').onclick = () => vscode.postMessage({ type: 'redo' });
|
|
||||||
document.getElementById('revert').onclick = () => vscode.postMessage({ type: 'revert' });
|
|
||||||
function esc(value) {
|
|
||||||
return String(value == null ? '' : value).replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]));
|
|
||||||
}
|
|
||||||
function table(rows) {
|
|
||||||
if (!Array.isArray(rows) || rows.length === 0 || typeof rows[0] !== 'object') return '<pre>' + esc(JSON.stringify(rows, null, 2)) + '</pre>';
|
|
||||||
const keys = Array.from(rows.reduce((set, row) => {
|
|
||||||
Object.keys(row || {}).slice(0, 12).forEach(key => set.add(key));
|
|
||||||
return set;
|
|
||||||
}, new Set()));
|
|
||||||
return '<table><thead><tr>' + keys.map(k => '<th>' + esc(k) + '</th>').join('') + '</tr></thead><tbody>' +
|
|
||||||
rows.slice(0, 1000).map(row => '<tr>' + keys.map(k => '<td>' + esc(typeof row[k] === 'object' ? JSON.stringify(row[k]) : row[k]) + '</td>').join('') + '</tr>').join('') +
|
|
||||||
'</tbody></table>' + (rows.length > 1000 ? '<p>Showing first 1000 rows.</p>' : '');
|
|
||||||
}
|
|
||||||
function renderHexRows(rows) {
|
|
||||||
return '<table class="hex-table"><thead><tr><th>Offset</th><th>Hex</th><th>ASCII</th></tr></thead><tbody>' +
|
|
||||||
rows.map(row => {
|
|
||||||
const cells = Array.isArray(row.bytes) ? row.bytes.map(cell => '<span class="hex-cell">' + esc(cell && cell.hex ? cell.hex : cell) + '</span>').join('') : esc(row.hex || '');
|
|
||||||
return '<tr><td>' + esc(row.offset) + '</td><td>' + cells + '</td><td class="ascii">' + esc(row.ascii || '') + '</td></tr>';
|
|
||||||
}).join('') + '</tbody></table>';
|
|
||||||
}
|
|
||||||
function render(value) {
|
|
||||||
if (value && value.mime === 'image/png' && value.pngBase64) {
|
|
||||||
const summary = value.summary ? '<h2>Summary</h2>' + render(value.summary) : '';
|
|
||||||
return '<img src="data:image/png;base64,' + value.pngBase64 + '">' + summary;
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) return table(value);
|
|
||||||
if (!value || typeof value !== 'object') return '<pre>' + esc(value) + '</pre>';
|
|
||||||
if (Array.isArray(value.rows) && value.rows.length && Object.prototype.hasOwnProperty.call(value.rows[0], 'ascii')) {
|
|
||||||
return renderHexRows(value.rows);
|
|
||||||
}
|
|
||||||
const preferred = ['rows', 'items', 'hits', 'results', 'entries', 'plan'];
|
|
||||||
let html = '';
|
|
||||||
for (const key of preferred) {
|
|
||||||
if (Array.isArray(value[key])) html += '<h2>' + esc(key) + '</h2>' + table(value[key]);
|
|
||||||
}
|
|
||||||
for (const [key, val] of Object.entries(value)) {
|
|
||||||
if (preferred.includes(key) && Array.isArray(val)) continue;
|
|
||||||
html += '<h2>' + esc(key) + '</h2>' + (Array.isArray(val) ? table(val) : '<pre>' + esc(JSON.stringify(val, null, 2)) + '</pre>');
|
|
||||||
}
|
|
||||||
return html || '<pre>' + esc(JSON.stringify(value, null, 2)) + '</pre>';
|
|
||||||
}
|
|
||||||
function renderFileState(s) {
|
|
||||||
document.getElementById('fileState').textContent = s && s.name ? s.name + ' • ' + Number(s.size || 0).toLocaleString() + ' bytes' + (s.dirty ? ' • modified' : '') : 'No file loaded';
|
|
||||||
}
|
|
||||||
window.addEventListener('message', event => {
|
|
||||||
const msg = event.data || {};
|
|
||||||
if (msg.type === 'result') document.getElementById('out').innerHTML = render(msg.result);
|
|
||||||
if (msg.type === 'state') document.getElementById('out').innerHTML = render(msg.state);
|
|
||||||
if (msg.type === 'fileState') renderFileState(msg.state);
|
|
||||||
if (msg.type === 'error') document.getElementById('out').textContent = msg.error;
|
|
||||||
});
|
|
||||||
renderFileState(initialFileState);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadFile(bridge, compare, view) {
|
|
||||||
const picked = await vscode.window.showOpenDialog({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false });
|
|
||||||
if (!picked || !picked.length) return null;
|
|
||||||
await bridge.rpc('LeviathanLoadFile', [picked[0].fsPath, compare]);
|
|
||||||
if (view) await view.refreshState();
|
|
||||||
return bridge.rpc('LeviathanState');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveFile(bridge, view) {
|
|
||||||
const state = await bridge.rpc('LeviathanState');
|
|
||||||
const defaultName = state && state.name ? `${state.name}.patched` : 'working-copy.bin';
|
|
||||||
const picked = await vscode.window.showSaveDialog({ defaultUri: vscode.Uri.file(defaultName) });
|
|
||||||
if (!picked) return;
|
|
||||||
await bridge.rpc('LeviathanSaveWorkingToPath', [picked.fsPath]);
|
|
||||||
await view.refreshState();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureLoadedFile(bridge, toolsView) {
|
|
||||||
try {
|
|
||||||
const state = await bridge.rpc('LeviathanState');
|
|
||||||
if (state && state.name) return state;
|
|
||||||
} catch (_) {
|
|
||||||
// Fall through to picker.
|
|
||||||
}
|
|
||||||
const loaded = await loadFile(bridge, false, toolsView);
|
|
||||||
if (!loaded || !loaded.name) {
|
|
||||||
throw new Error('Load a file before opening a Leviathan tool.');
|
|
||||||
}
|
|
||||||
return loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openTool(context, bridge, toolsView, tool) {
|
|
||||||
const state = await ensureLoadedFile(bridge, toolsView);
|
|
||||||
const autoRun = !['case', 'patch-apply', 'spectrogram-encode'].includes(tool);
|
|
||||||
new ToolPanel(context, bridge, tool, { autoRun, fileState: state });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openSelectedTool(context, bridge, toolsView, tool, resource, selectedResources) {
|
|
||||||
const target = Array.isArray(selectedResources) && selectedResources.length > 0
|
|
||||||
? selectedResources[0]
|
|
||||||
: resource;
|
|
||||||
if (!target || target.scheme !== 'file') {
|
|
||||||
vscode.window.showErrorMessage('Select a local file before opening it in Leviathan.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await bridge.rpc('LeviathanLoadFile', [target.fsPath, false]);
|
|
||||||
await toolsView.refreshState();
|
|
||||||
const state = await bridge.rpc('LeviathanState');
|
|
||||||
const autoRun = !['case', 'patch-apply', 'spectrogram-encode'].includes(tool);
|
|
||||||
new ToolPanel(context, bridge, tool, { autoRun, fileState: state });
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(value) {
|
|
||||||
return String(value).replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch]));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function activate(context) {
|
|
||||||
const bridge = new Bridge(context);
|
|
||||||
await bridge.start();
|
|
||||||
const toolsView = new ToolsView(context, bridge);
|
|
||||||
context.subscriptions.push(vscode.window.registerWebviewViewProvider('leviathan.tools', toolsView));
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('leviathan.loadFile', () => loadFile(bridge, false, toolsView)));
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('leviathan.loadCompare', () => loadFile(bridge, true, toolsView)));
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('leviathan.saveFile', () => saveFile(bridge, toolsView)));
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('leviathan.openTool', tool => openTool(context, bridge, toolsView, tool)));
|
|
||||||
for (const tool of TOOLS) {
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand(
|
|
||||||
`leviathan.openSelected.${tool.id}`,
|
|
||||||
(resource, selectedResources) => openSelectedTool(context, bridge, toolsView, tool.id, resource, selectedResources)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deactivate() {}
|
|
||||||
|
|
||||||
module.exports = { activate, deactivate };
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M13.4 3.5H5a1.5 1.5 0 0 0-1.5 1.5v14A1.5 1.5 0 0 0 5 20.5h5.2"/>
|
|
||||||
<path d="M6.6 7.3h7"/>
|
|
||||||
<path d="M6.6 10.4h4.4"/>
|
|
||||||
<path d="M6.6 13.5h2.2"/>
|
|
||||||
<circle cx="15.3" cy="14.1" r="3.7"/>
|
|
||||||
<path d="m18 16.8 2.8 2.8"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 396 B |
@@ -1,312 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "leviathan",
|
|
||||||
"displayName": "Leviathan",
|
|
||||||
"description": "Leviathan binary analysis tools inside Visual Studio Code.",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"publisher": "andydixon",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.90.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"extension.js",
|
|
||||||
"package.json",
|
|
||||||
"media/**",
|
|
||||||
"bin/**"
|
|
||||||
],
|
|
||||||
"activationEvents": [
|
|
||||||
"onView:leviathan.tools",
|
|
||||||
"onCommand:leviathan.openSelected.hex",
|
|
||||||
"onCommand:leviathan.openSelected.overview",
|
|
||||||
"onCommand:leviathan.openSelected.hashes",
|
|
||||||
"onCommand:leviathan.openSelected.entropy",
|
|
||||||
"onCommand:leviathan.openSelected.strings",
|
|
||||||
"onCommand:leviathan.openSelected.iocs",
|
|
||||||
"onCommand:leviathan.openSelected.search",
|
|
||||||
"onCommand:leviathan.openSelected.multi-search",
|
|
||||||
"onCommand:leviathan.openSelected.parse",
|
|
||||||
"onCommand:leviathan.openSelected.disasm",
|
|
||||||
"onCommand:leviathan.openSelected.pdf",
|
|
||||||
"onCommand:leviathan.openSelected.squashfs",
|
|
||||||
"onCommand:leviathan.openSelected.spectrogram-encode",
|
|
||||||
"onCommand:leviathan.openSelected.spectrogram-view",
|
|
||||||
"onCommand:leviathan.openSelected.diff",
|
|
||||||
"onCommand:leviathan.openSelected.carve",
|
|
||||||
"onCommand:leviathan.openSelected.keys",
|
|
||||||
"onCommand:leviathan.openSelected.timestamps",
|
|
||||||
"onCommand:leviathan.openSelected.stego",
|
|
||||||
"onCommand:leviathan.openSelected.metadata",
|
|
||||||
"onCommand:leviathan.openSelected.binsnoop",
|
|
||||||
"onCommand:leviathan.openSelected.rules",
|
|
||||||
"onCommand:leviathan.openSelected.patch-plan",
|
|
||||||
"onCommand:leviathan.openSelected.patch-apply",
|
|
||||||
"onCommand:leviathan.openSelected.transform",
|
|
||||||
"onCommand:leviathan.openSelected.case"
|
|
||||||
],
|
|
||||||
"main": "./extension.js",
|
|
||||||
"contributes": {
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
"command": "leviathan.loadFile",
|
|
||||||
"title": "Load Binary"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.loadCompare",
|
|
||||||
"title": "Load Compare Binary"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.saveFile",
|
|
||||||
"title": "Save Working Copy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openTool",
|
|
||||||
"title": "Open Leviathan Tool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.hex",
|
|
||||||
"title": "Hex Editor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.overview",
|
|
||||||
"title": "Overview"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.hashes",
|
|
||||||
"title": "Hashes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.entropy",
|
|
||||||
"title": "Entropy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.strings",
|
|
||||||
"title": "Strings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.iocs",
|
|
||||||
"title": "IOCs"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.search",
|
|
||||||
"title": "Search"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.multi-search",
|
|
||||||
"title": "Multi-search"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.parse",
|
|
||||||
"title": "Structures"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.disasm",
|
|
||||||
"title": "Disasm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.pdf",
|
|
||||||
"title": "PDF"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.squashfs",
|
|
||||||
"title": "SquashFS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.spectrogram-encode",
|
|
||||||
"title": "Spectrogram Encode"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.spectrogram-view",
|
|
||||||
"title": "Spectrogram View"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.diff",
|
|
||||||
"title": "Diff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.carve",
|
|
||||||
"title": "Carve"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.keys",
|
|
||||||
"title": "Keys"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.timestamps",
|
|
||||||
"title": "Timestamps"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.stego",
|
|
||||||
"title": "Stego"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.metadata",
|
|
||||||
"title": "Metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.binsnoop",
|
|
||||||
"title": "Binsnoop"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.rules",
|
|
||||||
"title": "Rules"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.patch-plan",
|
|
||||||
"title": "Patch Plan"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.patch-apply",
|
|
||||||
"title": "Patch Apply"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.transform",
|
|
||||||
"title": "Transforms"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.case",
|
|
||||||
"title": "Case Export"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"submenus": [
|
|
||||||
{
|
|
||||||
"id": "leviathan.contextTools",
|
|
||||||
"label": "Leviathan"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"viewsContainers": {
|
|
||||||
"activitybar": [
|
|
||||||
{
|
|
||||||
"id": "leviathan",
|
|
||||||
"title": "Leviathan",
|
|
||||||
"icon": "media/leviathan.svg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"views": {
|
|
||||||
"leviathan": [
|
|
||||||
{
|
|
||||||
"id": "leviathan.tools",
|
|
||||||
"name": "Tools",
|
|
||||||
"type": "webview"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"menus": {
|
|
||||||
"explorer/context": [
|
|
||||||
{
|
|
||||||
"submenu": "leviathan.contextTools",
|
|
||||||
"when": "resourceScheme == file && explorerResourceIsFolder == false",
|
|
||||||
"group": "navigation"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"leviathan.contextTools": [
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.hex",
|
|
||||||
"group": "01_editor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.overview",
|
|
||||||
"group": "02_triage@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.hashes",
|
|
||||||
"group": "02_triage@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.entropy",
|
|
||||||
"group": "02_triage@3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.strings",
|
|
||||||
"group": "02_triage@4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.iocs",
|
|
||||||
"group": "02_triage@5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.search",
|
|
||||||
"group": "03_find@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.multi-search",
|
|
||||||
"group": "03_find@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.parse",
|
|
||||||
"group": "04_structure@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.disasm",
|
|
||||||
"group": "04_structure@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.pdf",
|
|
||||||
"group": "04_structure@3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.squashfs",
|
|
||||||
"group": "04_structure@4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.spectrogram-encode",
|
|
||||||
"group": "05_audio@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.spectrogram-view",
|
|
||||||
"group": "05_audio@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.diff",
|
|
||||||
"group": "06_compare"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.carve",
|
|
||||||
"group": "07_recover@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.keys",
|
|
||||||
"group": "07_recover@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.timestamps",
|
|
||||||
"group": "07_recover@3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.stego",
|
|
||||||
"group": "07_recover@4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.metadata",
|
|
||||||
"group": "07_recover@5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.binsnoop",
|
|
||||||
"group": "08_advanced@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.rules",
|
|
||||||
"group": "08_advanced@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.patch-plan",
|
|
||||||
"group": "09_edit@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.patch-apply",
|
|
||||||
"group": "09_edit@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.transform",
|
|
||||||
"group": "09_edit@3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "leviathan.openSelected.case",
|
|
||||||
"group": "10_case"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M12 2.5 20.5 7.25v9.5L12 21.5 3.5 16.75v-9.5L12 2.5Z"/>
|
|
||||||
<path d="m8.5 9.7 2.8 2.3-2.8 2.3"/>
|
|
||||||
<path d="M12.6 15h3.1"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 301 B |
Vendored
-7
File diff suppressed because one or more lines are too long
-1
@@ -1 +0,0 @@
|
|||||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core._renderService.dimensions;if(0===e.css.cell.width||0===e.css.cell.height)return;const t=0===this._terminal.options.scrollback?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),i=parseInt(r.getPropertyValue("height")),o=Math.max(0,parseInt(r.getPropertyValue("width"))),s=window.getComputedStyle(this._terminal.element),n=i-(parseInt(s.getPropertyValue("padding-top"))+parseInt(s.getPropertyValue("padding-bottom"))),l=o-(parseInt(s.getPropertyValue("padding-right"))+parseInt(s.getPropertyValue("padding-left")))-t;return{cols:Math.max(2,Math.floor(l/e.css.cell.width)),rows:Math.max(1,Math.floor(n/e.css.cell.height))}}}})(),e})()));
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,285 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
||||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
||||||
* https://github.com/chjj/term.js
|
|
||||||
* @license MIT
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* Originally forked from (with the author's permission):
|
|
||||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
||||||
* http://bellard.org/jslinux/
|
|
||||||
* Copyright (c) 2011 Fabrice Bellard
|
|
||||||
* The original design remains. The terminal itself
|
|
||||||
* has been extended to include xterm CSI codes, among
|
|
||||||
* other features.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default styles for xterm.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
.xterm {
|
|
||||||
cursor: text;
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm.focus,
|
|
||||||
.xterm:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-helpers {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
/**
|
|
||||||
* The z-index of the helpers must be higher than the canvases in order for
|
|
||||||
* IMEs to appear on top.
|
|
||||||
*/
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-helper-textarea {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
left: -9999em;
|
|
||||||
top: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
z-index: -5;
|
|
||||||
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .composition-view {
|
|
||||||
/* TODO: Composition position got messed up somewhere */
|
|
||||||
background: #000;
|
|
||||||
color: #FFF;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
white-space: nowrap;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .composition-view.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-viewport {
|
|
||||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
|
||||||
background-color: #000;
|
|
||||||
overflow-y: scroll;
|
|
||||||
cursor: default;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-screen {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-screen canvas {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-char-measure-element {
|
|
||||||
display: inline-block;
|
|
||||||
visibility: hidden;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -9999em;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm.enable-mouse-events {
|
|
||||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm.xterm-cursor-pointer,
|
|
||||||
.xterm .xterm-cursor-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm.column-select.focus {
|
|
||||||
/* Column selection mode */
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-accessibility:not(.debug),
|
|
||||||
.xterm .xterm-message {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 10;
|
|
||||||
color: transparent;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-accessibility-tree:not(.debug) *::selection {
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-accessibility-tree {
|
|
||||||
font-family: monospace;
|
|
||||||
user-select: text;
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-accessibility-tree > div {
|
|
||||||
transform-origin: left;
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .live-region {
|
|
||||||
position: absolute;
|
|
||||||
left: -9999px;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-dim {
|
|
||||||
/* Dim should not apply to background, so the opacity of the foreground color is applied
|
|
||||||
* explicitly in the generated class and reset to 1 here */
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-underline-1 { text-decoration: underline; }
|
|
||||||
.xterm-underline-2 { text-decoration: double underline; }
|
|
||||||
.xterm-underline-3 { text-decoration: wavy underline; }
|
|
||||||
.xterm-underline-4 { text-decoration: dotted underline; }
|
|
||||||
.xterm-underline-5 { text-decoration: dashed underline; }
|
|
||||||
|
|
||||||
.xterm-overline {
|
|
||||||
text-decoration: overline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
|
|
||||||
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
|
|
||||||
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
|
|
||||||
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
|
|
||||||
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
|
|
||||||
|
|
||||||
.xterm-strikethrough {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
|
||||||
z-index: 6;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
|
|
||||||
z-index: 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-decoration-overview-ruler {
|
|
||||||
z-index: 8;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm-decoration-top {
|
|
||||||
z-index: 2;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */
|
|
||||||
|
|
||||||
/* xterm.js customization: Override xterm's cursor style */
|
|
||||||
.xterm .xterm-scrollable-element > .scrollbar {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Arrows */
|
|
||||||
.xterm .xterm-scrollable-element > .scrollbar > .scra {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 11px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.xterm .xterm-scrollable-element > .visible {
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
/* Background rule added for IE9 - to allow clicks on dom node */
|
|
||||||
background:rgba(0,0,0,0);
|
|
||||||
|
|
||||||
transition: opacity 100ms linear;
|
|
||||||
/* In front of peek view */
|
|
||||||
z-index: 11;
|
|
||||||
}
|
|
||||||
.xterm .xterm-scrollable-element > .invisible {
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.xterm .xterm-scrollable-element > .invisible.fade {
|
|
||||||
transition: opacity 800ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollable Content Inset Shadow */
|
|
||||||
.xterm .xterm-scrollable-element > .shadow {
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.xterm .xterm-scrollable-element > .shadow.top {
|
|
||||||
display: block;
|
|
||||||
top: 0;
|
|
||||||
left: 3px;
|
|
||||||
height: 3px;
|
|
||||||
width: 100%;
|
|
||||||
box-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset;
|
|
||||||
}
|
|
||||||
.xterm .xterm-scrollable-element > .shadow.left {
|
|
||||||
display: block;
|
|
||||||
top: 3px;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 3px;
|
|
||||||
box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset;
|
|
||||||
}
|
|
||||||
.xterm .xterm-scrollable-element > .shadow.top-left-corner {
|
|
||||||
display: block;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 3px;
|
|
||||||
width: 3px;
|
|
||||||
}
|
|
||||||
.xterm .xterm-scrollable-element > .shadow.top.left {
|
|
||||||
box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset;
|
|
||||||
}
|
|
||||||
Vendored
-1
File diff suppressed because one or more lines are too long
@@ -1,112 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nexus",
|
|
||||||
"displayName": "Nexus",
|
|
||||||
"description": "Nexus terminal connections inside Visual Studio Code.",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"publisher": "andydixon",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.90.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"Other"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"extension.js",
|
|
||||||
"package.json",
|
|
||||||
"media/**",
|
|
||||||
"bin/**"
|
|
||||||
],
|
|
||||||
"activationEvents": [
|
|
||||||
"onView:nexus.connections",
|
|
||||||
"onCommand:nexus.newProfile",
|
|
||||||
"onCommand:nexus.openSettings"
|
|
||||||
],
|
|
||||||
"main": "./extension.js",
|
|
||||||
"contributes": {
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
"command": "nexus.refreshConnections",
|
|
||||||
"title": "Refresh Connections"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.connectLocal",
|
|
||||||
"title": "Connect Local Terminal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.openConnection",
|
|
||||||
"title": "Open Nexus Connection"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.newProfile",
|
|
||||||
"title": "New Nexus Profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.editProfile",
|
|
||||||
"title": "Edit Nexus Profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.duplicateProfile",
|
|
||||||
"title": "Duplicate Nexus Profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.deleteProfile",
|
|
||||||
"title": "Delete Nexus Profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.openSettings",
|
|
||||||
"title": "Nexus Settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.openSftp",
|
|
||||||
"title": "Open SFTP Browser"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"viewsContainers": {
|
|
||||||
"activitybar": [
|
|
||||||
{
|
|
||||||
"id": "nexus",
|
|
||||||
"title": "Nexus",
|
|
||||||
"icon": "media/nexus.svg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"views": {
|
|
||||||
"nexus": [
|
|
||||||
{
|
|
||||||
"id": "nexus.connections",
|
|
||||||
"name": "Connections",
|
|
||||||
"type": "webview"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"menus": {
|
|
||||||
"view/item/context": [
|
|
||||||
{
|
|
||||||
"command": "nexus.openConnection",
|
|
||||||
"when": "view == nexus.connections && viewItem == profile",
|
|
||||||
"group": "inline"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.editProfile",
|
|
||||||
"when": "view == nexus.connections && viewItem == profile",
|
|
||||||
"group": "navigation@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.duplicateProfile",
|
|
||||||
"when": "view == nexus.connections && viewItem == profile",
|
|
||||||
"group": "navigation@2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.deleteProfile",
|
|
||||||
"when": "view == nexus.connections && viewItem == profile",
|
|
||||||
"group": "navigation@3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "nexus.openSftp",
|
|
||||||
"when": "view == nexus.connections && viewItem == profile",
|
|
||||||
"group": "navigation@4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# Build platform-specific VSIX packages for the Nexus and Leviathan VSCode
|
|
||||||
# extensions, covering Windows, macOS and Linux on both x64 and arm64.
|
|
||||||
#
|
|
||||||
# Each extension embeds a Go "bridge" binary that is OS/arch specific, so we
|
|
||||||
# produce one VSIX per target using VSCode's platform-specific extension
|
|
||||||
# mechanism (`vsce package --target <target>`). VSCode then installs the VSIX
|
|
||||||
# matching the user's platform automatically (e.g. from the Marketplace, or via
|
|
||||||
# `code --install-extension <file>.vsix`).
|
|
||||||
#
|
|
||||||
# The bridge is pure Go (CGO_ENABLED=0), so every target cross-compiles from any
|
|
||||||
# host with just the Go toolchain — no C compilers or per-OS toolchains needed.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# vscode/package-all.sh # build every target
|
|
||||||
# vscode/package-all.sh linux-x64 ... # build only the named vsce target(s)
|
|
||||||
#
|
|
||||||
# Optional macOS code signing + notarization (darwin targets only):
|
|
||||||
# Runs only on a macOS host with Xcode command line tools, and only when the
|
|
||||||
# relevant env vars are set — otherwise it is skipped with a warning and the
|
|
||||||
# darwin VSIXs are produced unsigned (fine for local use, blocked by Gatekeeper
|
|
||||||
# when distributed to other machines).
|
|
||||||
#
|
|
||||||
# MACOS_SIGN_IDENTITY "Developer ID Application: Name (TEAMID)" — enables signing.
|
|
||||||
#
|
|
||||||
# Then, to also notarize, provide EITHER a stored notarytool profile:
|
|
||||||
# MACOS_NOTARY_PROFILE profile name from `xcrun notarytool store-credentials`
|
|
||||||
# OR an Apple ID + app-specific password:
|
|
||||||
# MACOS_NOTARY_APPLE_ID Apple ID email
|
|
||||||
# MACOS_NOTARY_TEAM_ID Apple Developer team id
|
|
||||||
# MACOS_NOTARY_PASSWORD app-specific password
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# MACOS_SIGN_IDENTITY="Developer ID Application: Andy Dixon (AB12CD34EF)" \
|
|
||||||
# MACOS_NOTARY_PROFILE="nexus-notary" \
|
|
||||||
# vscode/package-all.sh darwin-x64 darwin-arm64
|
|
||||||
#
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
VSCODE_DIR="$ROOT_DIR/vscode"
|
|
||||||
OUT_DIR="$VSCODE_DIR/dist"
|
|
||||||
EXTENSIONS=(nexus leviathan)
|
|
||||||
|
|
||||||
# vsce target -> GOOS/GOARCH
|
|
||||||
ALL_TARGETS=(
|
|
||||||
"win32-x64:windows/amd64"
|
|
||||||
"win32-arm64:windows/arm64"
|
|
||||||
"darwin-x64:darwin/amd64"
|
|
||||||
"darwin-arm64:darwin/arm64"
|
|
||||||
"linux-x64:linux/amd64"
|
|
||||||
"linux-arm64:linux/arm64"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Optional positional filter: only build the vsce targets passed as arguments.
|
|
||||||
SELECTED=("$@")
|
|
||||||
target_selected() {
|
|
||||||
[ ${#SELECTED[@]} -eq 0 ] && return 0
|
|
||||||
local want="$1"
|
|
||||||
for t in "${SELECTED[@]}"; do [ "$t" = "$want" ] && return 0; done
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
run_vsce() {
|
|
||||||
if command -v vsce >/dev/null 2>&1; then
|
|
||||||
vsce "$@"
|
|
||||||
else
|
|
||||||
npx --yes @vscode/vsce "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Codesign (hardened runtime) and optionally notarize a single darwin bridge
|
|
||||||
# binary. Opt-in via env vars and skipped with a warning when it can't run, so
|
|
||||||
# the cross-platform build still succeeds on non-macOS hosts. Signing the staged
|
|
||||||
# binary before it is copied into each extension means both VSIXs inherit the
|
|
||||||
# signature (and notarization, which is keyed to the binary's hash).
|
|
||||||
macos_sign() {
|
|
||||||
local bin="$1"
|
|
||||||
|
|
||||||
if [ -z "${MACOS_SIGN_IDENTITY:-}" ]; then
|
|
||||||
echo "[vscode] (skip signing: MACOS_SIGN_IDENTITY not set — shipping unsigned)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ "$(uname -s)" != "Darwin" ] || ! command -v codesign >/dev/null 2>&1; then
|
|
||||||
echo "[vscode] WARNING: cannot sign darwin bridge here (needs macOS + Xcode tools); shipping unsigned"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[vscode] codesign ($MACOS_SIGN_IDENTITY)"
|
|
||||||
codesign --force --options runtime --timestamp --sign "$MACOS_SIGN_IDENTITY" "$bin"
|
|
||||||
codesign --verify --strict --verbose=2 "$bin"
|
|
||||||
|
|
||||||
# Notarize only when credentials are provided.
|
|
||||||
local notary_args=()
|
|
||||||
if [ -n "${MACOS_NOTARY_PROFILE:-}" ]; then
|
|
||||||
notary_args=(--keychain-profile "$MACOS_NOTARY_PROFILE")
|
|
||||||
elif [ -n "${MACOS_NOTARY_APPLE_ID:-}" ] && [ -n "${MACOS_NOTARY_TEAM_ID:-}" ] && [ -n "${MACOS_NOTARY_PASSWORD:-}" ]; then
|
|
||||||
notary_args=(--apple-id "$MACOS_NOTARY_APPLE_ID" --team-id "$MACOS_NOTARY_TEAM_ID" --password "$MACOS_NOTARY_PASSWORD")
|
|
||||||
else
|
|
||||||
echo "[vscode] (skip notarization: no notary credentials set)"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if ! command -v xcrun >/dev/null 2>&1; then
|
|
||||||
echo "[vscode] WARNING: xcrun not found; cannot notarize"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "[vscode] notarize (submitting to Apple, waiting for result…)"
|
|
||||||
local zip="${bin}.notarize.zip"
|
|
||||||
# notarytool requires an archive; a bare Mach-O binary cannot be submitted directly.
|
|
||||||
ditto -c -k --keepParent "$bin" "$zip"
|
|
||||||
xcrun notarytool submit "$zip" "${notary_args[@]}" --wait
|
|
||||||
rm -f "$zip"
|
|
||||||
# A standalone executable cannot be stapled (stapler only supports app/dmg/pkg);
|
|
||||||
# Gatekeeper verifies the notarization online via the now-registered hash, which
|
|
||||||
# matches the exact bytes embedded in every VSIX.
|
|
||||||
echo "[vscode] notarized (online check; standalone binaries are not stapleable)"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "[vscode] Building frontend assets (platform independent)"
|
|
||||||
if [ ! -d "$ROOT_DIR/frontend/node_modules" ]; then
|
|
||||||
npm --prefix "$ROOT_DIR/frontend" install
|
|
||||||
fi
|
|
||||||
npm --prefix "$ROOT_DIR/frontend" run build
|
|
||||||
|
|
||||||
echo "[vscode] Staging shared media assets"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm.min.js" "$VSCODE_DIR/nexus/media/xterm.min.js"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm-addon-fit.min.js" "$VSCODE_DIR/nexus/media/xterm-addon-fit.min.js"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/xterm-addon-unicode11.min.js" "$VSCODE_DIR/nexus/media/xterm-addon-unicode11.min.js"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/css/lib/xterm.css" "$VSCODE_DIR/nexus/media/xterm.css"
|
|
||||||
cp "$ROOT_DIR/frontend/dist/src/js/lib/novnc.min.js" "$VSCODE_DIR/nexus/media/novnc.min.js"
|
|
||||||
|
|
||||||
rm -rf "$OUT_DIR"
|
|
||||||
mkdir -p "$OUT_DIR"
|
|
||||||
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
VERSION="$(node -p "require('$VSCODE_DIR/nexus/package.json').version")"
|
|
||||||
|
|
||||||
for entry in "${ALL_TARGETS[@]}"; do
|
|
||||||
VSCE_TARGET="${entry%%:*}"
|
|
||||||
GOPLATFORM="${entry##*:}"
|
|
||||||
GOOS="${GOPLATFORM%%/*}"
|
|
||||||
GOARCH="${GOPLATFORM##*/}"
|
|
||||||
|
|
||||||
if ! target_selected "$VSCE_TARGET"; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
BIN_NAME="nexus-vscode-bridge"
|
|
||||||
[ "$GOOS" = "windows" ] && BIN_NAME="nexus-vscode-bridge.exe"
|
|
||||||
|
|
||||||
echo "[vscode] Building bridge for $VSCE_TARGET ($GOOS/$GOARCH)"
|
|
||||||
STAGE_BIN="$VSCODE_DIR/bin/$VSCE_TARGET/$BIN_NAME"
|
|
||||||
mkdir -p "$(dirname "$STAGE_BIN")"
|
|
||||||
GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 \
|
|
||||||
go build -trimpath -o "$STAGE_BIN" ./cmd/vscode-bridge
|
|
||||||
|
|
||||||
# Sign + notarize macOS bridges before they are copied into the extensions.
|
|
||||||
if [ "$GOOS" = "darwin" ]; then
|
|
||||||
macos_sign "$STAGE_BIN"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for EXT in "${EXTENSIONS[@]}"; do
|
|
||||||
EXT_BIN_DIR="$VSCODE_DIR/$EXT/bin"
|
|
||||||
# Ship exactly one binary per VSIX so the package can't carry a wrong-arch
|
|
||||||
# (or wrong-OS) bridge alongside the right one.
|
|
||||||
rm -rf "$EXT_BIN_DIR"
|
|
||||||
mkdir -p "$EXT_BIN_DIR"
|
|
||||||
cp "$STAGE_BIN" "$EXT_BIN_DIR/$BIN_NAME"
|
|
||||||
chmod 0755 "$EXT_BIN_DIR/$BIN_NAME"
|
|
||||||
|
|
||||||
OUT_VSIX="$OUT_DIR/$EXT-$VERSION-$VSCE_TARGET.vsix"
|
|
||||||
( cd "$VSCODE_DIR/$EXT" && run_vsce package --target "$VSCE_TARGET" \
|
|
||||||
--no-dependencies --allow-missing-repository -o "$OUT_VSIX" >/dev/null )
|
|
||||||
echo "[vscode] wrote $(basename "$OUT_VSIX")"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
# Leave the working tree with a host-native bridge staged so a plain
|
|
||||||
# `code --install-extension` of an unpacked extension still works locally.
|
|
||||||
HOST_OS="$(go env GOOS)"; HOST_ARCH="$(go env GOARCH)"
|
|
||||||
HOST_BIN="nexus-vscode-bridge"; [ "$HOST_OS" = "windows" ] && HOST_BIN="nexus-vscode-bridge.exe"
|
|
||||||
for EXT in "${EXTENSIONS[@]}"; do
|
|
||||||
rm -rf "$VSCODE_DIR/$EXT/bin"; mkdir -p "$VSCODE_DIR/$EXT/bin"
|
|
||||||
GOOS="$HOST_OS" GOARCH="$HOST_ARCH" CGO_ENABLED=0 \
|
|
||||||
go build -trimpath -o "$VSCODE_DIR/$EXT/bin/$HOST_BIN" ./cmd/vscode-bridge
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[vscode] Done. Packages in $OUT_DIR:"
|
|
||||||
ls -1 "$OUT_DIR"
|
|
||||||
Reference in New Issue
Block a user