diff --git a/.github/workflows/build-windows-installer.yml b/.github/workflows/build-windows-installer.yml new file mode 100644 index 00000000..365c92d3 --- /dev/null +++ b/.github/workflows/build-windows-installer.yml @@ -0,0 +1,103 @@ +name: Build Windows Linux-CLI Installer + +on: + workflow_dispatch: + inputs: + upload_release: + description: 'Upload as a GitHub Release (requires a version tag)' + type: boolean + default: false + push: + branches: + - windows-offline-installer + tags: + - 'linux-cli-v*' + +jobs: + build: + name: Build installer (${{ matrix.arch }}) + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + runner: windows-latest + - arch: arm64 + runner: windows-11-arm + runs-on: ${{ matrix.runner }} + continue-on-error: ${{ matrix.arch == 'arm64' }} + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + architecture: ${{ matrix.arch }} + + - name: Install dependencies and compile TypeScript + shell: pwsh + run: | + npm ci + npm run build + + - name: Stage installer bundle + shell: pwsh + run: | + & windows-installer/stage.ps1 ` + -Arch "${{ matrix.arch }}" ` + -NodeVersion "22.15.1" + if ($LASTEXITCODE -gt 1) { exit $LASTEXITCODE } + exit 0 + + - name: Install NSIS + shell: pwsh + run: choco install nsis --no-progress -y + + - name: Build NSIS installer + shell: pwsh + run: | + $version = (Get-Content package.json | ConvertFrom-Json).version + Set-Location windows-installer + New-Item -ItemType Directory -Force output | Out-Null + + $makeNsis = $null + $candidates = @( + "$env:ChocolateyInstall\bin\makensis.exe", + "$env:ProgramData\chocolatey\bin\makensis.exe", + "$env:ProgramFiles\NSIS\makensis.exe", + "C:\Program Files (x86)\NSIS\makensis.exe" + ) + + foreach ($candidate in $candidates) { + if (Test-Path $candidate) { + $makeNsis = $candidate + break + } + } + + if (-not $makeNsis) { + $cmd = Get-Command makensis -ErrorAction SilentlyContinue + if ($cmd) { $makeNsis = $cmd.Source } + } + + if (-not $makeNsis) { + throw "makensis.exe not found after NSIS install" + } + + & $makeNsis /DARCH=${{ matrix.arch }} /DPRODUCT_VERSION=$version installer.nsi + + - name: Upload installer artifact + uses: actions/upload-artifact@v4 + with: + name: edge-impulse-linux-cli-windows-${{ matrix.arch }} + path: windows-installer/output/edge-impulse-linux-cli-windows-${{ matrix.arch }}-setup.exe + if-no-files-found: error + + - name: Upload to GitHub Release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.upload_release) + uses: softprops/action-gh-release@v2 + with: + files: windows-installer/output/edge-impulse-linux-cli-windows-${{ matrix.arch }}-setup.exe + tag_name: ${{ github.ref_name }} diff --git a/README.md b/README.md index 68f70e02..13c849f1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,48 @@ Add the library to your application via: $ npm install edge-impulse-linux ``` +## Windows offline installer (for locked-down environments) + +For corporate-managed Windows devices where `npm install` is blocked (TLS interception, no build tools, restricted package access), this repo also supports a prebuilt Windows installer artifact via GitHub Actions. + +### What this installer includes + +* Bundled `node.exe` runtime (no separate Node.js install required) +* Prebuilt `node_modules` from CI (no local `node-gyp` / Python toolchain required) +* Installed CLI shims in PATH: + * `edge-impulse-linux` + * `edge-impulse-linux-runner` + * `edge-impulse-camera-debug` + +### End-user requirements + +* Windows 10/11 (`x64` or `arm64` artifact) +* Administrator rights to install (writes to `Program Files` and system PATH) +* WSL is not required for installation, but recommended for full Linux CLI behavior + +### Important runtime note + +This is still the Linux CLI package, packaged for Windows installation. Some commands or hardware flows that depend on Linux-specific behavior or drivers may still require Linux/WSL at runtime. + +For full functionality, install WSL first from an elevated Command Prompt: + +``` +wsl --install +``` + +### Build and download installer artifacts + +Use the workflow in this repository: + +* **Actions** → **Build Windows Linux-CLI Installer** + +Artifacts produced: + +* `edge-impulse-linux-cli-windows-x64` +* `edge-impulse-linux-cli-windows-arm64` + +Each artifact zip contains a `.exe` installer. + ## Collecting data Before you can classify data you'll first need to collect it. If you want to collect data from the camera or microphone on your system you can use the Edge Impulse CLI, and if you want to collect data from different sensors (like accelerometers or proprietary control systems) you can do so in a few lines of code. @@ -49,6 +91,59 @@ You can pass in options to the CLI. Here are the key ones: edge-impulse-linux and edge-impulse-linux-runner can be run as a service via a custom AWS IoT Greengrass component(s). When provided with the "--greengrass" option, both services will utilize the AWS IoT Greengrass authentication context (ONLY present when launched as a AWS IoT Greengrass custom component) as well as AWS Secrets Manager to extract the api key to be used to authenticate to a new project. If the authentication context is abscent and/or incorrect, both services will simply ignore the "--greengrass" option that was provided and continue with any of the other provided options normally. +### Snapdragon Hardware Acceleration (Windows ARM64) + +The Windows installer for ARM64 devices on Qualcomm Snapdragon platforms includes automatic detection and support for **QNN (Qualcomm Neural Network)** hardware acceleration. + +#### What this enables + +* Neural network inference via Snapdragon NPU (Neural Processing Unit) +* Optimized DSP (Digital Signal Processing) for audio/signal preprocessing +* Accelerated model compilation to ONNX and TensorFlow Lite formats +* Automatic fallback to CPU mode if acceleration libraries unavailable + +#### Supported devices + +* Windows 11 ARM64 on Snapdragon processors (X SoC, Gen 3, etc.) +* Qualcomm reference boards (RB3 Gen 2, IQ-9) + +#### Checking for Snapdragon acceleration + +After installation, run the included detection script to verify acceleration support: + +```powershell +# From Command Prompt or PowerShell +%PROGRAMFILES%\EdgeImpulse Linux CLI\bin\detect-snapdragon.ps1 +``` + +Expected output for Snapdragon device: + +``` +✓ Snapdragon device detected: +✓ QNN runtime available - hardware acceleration enabled +``` + +If QNN runtime is not detected: + +``` +ℹ Qualcomm Snapdragon detected: +ℹ QNN runtime not detected - install Qualcomm AI Hub or Snapdragon SDK for acceleration +``` + +#### Installing QNN runtime + +To enable QNN hardware acceleration, install one of: + +1. **Qualcomm AI Hub** - Recommended for developers + - Download from https://qualcomm.ai/hub + - Includes QNN SDK and runtime libraries + +2. **Snapdragon SDK** - For Snapdragon platform development + - Available via Qualcomm Developer Network + - Includes TensorFlow Lite with QNN delegate support + +After installation, the CLI will automatically detect and use QNN for model execution. The system gracefully falls back to CPU inference if QNN libraries are unavailable. + ## Classifying data To classify data (whether this is from the camera, the microphone, or a custom sensor) you'll need a model file. This model file contains all signal processing code, classical ML algorithms and neural networks - and typically contains hardware optimizations to run as fast as possible. To grab a model file: diff --git a/windows-installer/.gitignore b/windows-installer/.gitignore new file mode 100644 index 00000000..7d309a94 --- /dev/null +++ b/windows-installer/.gitignore @@ -0,0 +1,4 @@ +staging/ +output/ +# Only ignore generated sub-folder; header.bmp / welcome.bmp are pre-built and committed. +branding/generated/ diff --git a/windows-installer/branding/header.bmp b/windows-installer/branding/header.bmp new file mode 100644 index 00000000..d474802f Binary files /dev/null and b/windows-installer/branding/header.bmp differ diff --git a/windows-installer/branding/welcome.bmp b/windows-installer/branding/welcome.bmp new file mode 100644 index 00000000..f3122da4 Binary files /dev/null and b/windows-installer/branding/welcome.bmp differ diff --git a/windows-installer/detect-snapdragon.ps1 b/windows-installer/detect-snapdragon.ps1 new file mode 100644 index 00000000..5edf86ab --- /dev/null +++ b/windows-installer/detect-snapdragon.ps1 @@ -0,0 +1,97 @@ +# Snapdragon Hardware Acceleration Detector for Windows ARM64 +# This script detects Qualcomm Snapdragon devices and validates QNN (Qualcomm Neural Network) runtime support +# Output: JSON object with device info and QNN availability + +param( + [switch]$Json = $false +) + +# Detect if running on ARM64 Windows +$isArm64 = (Get-WmiObject -Class Win32_Processor | Select-Object -ExpandProperty Architecture) -eq 12 + +if (-not $isArm64) { + if ($Json) { + Write-Output (ConvertTo-Json @{ + "device" = "x64" + "isSnapdragon" = $false + "hasQnn" = $false + "message" = "Not an ARM64 device" + }) + } + else { + Write-Output "Not an ARM64 device (current: $(Get-WmiObject -Class Win32_Processor | Select-Object -ExpandProperty Name))" + } + exit 0 +} + +# Detect Qualcomm Snappdragon device indicators +$deviceInfo = @{ + "device" = "arm64" + "isSnapdragon" = $false + "hasQnn" = $false + "processorName" = "" + "message" = "" +} + +try { + $processor = Get-WmiObject -Class Win32_Processor | Select-Object -ExpandProperty Name + $deviceInfo.processorName = $processor + + # Check for Qualcomm Snapdragon indicators + if ($processor -like "*Qualcomm*" -or $processor -like "*Snapdragon*") { + $deviceInfo.isSnapdragon = $true + $deviceInfo.message = "Qualcomm Snapdragon detected: $processor" + } +} +catch { + $deviceInfo.message = "Warning: Could not detect processor details (error: $_)" +} + +# Check for QNN runtime library in WSL2 (if available) +# This would require WSL with Qualcomm AI Hub or Snapdragon SDK installed +$qnnPaths = @( + "$env:USERPROFILE\.qnn\lib\libQnnTFLiteDelegate.so", + "$env:ProgramFiles\Qualcomm\*\lib\*QNN*", + "C:\Program Files\Qualcomm AI Hub\*\lib\*QNN*" +) + +foreach ($path in $qnnPaths) { + if (Test-Path $path) { + $deviceInfo.hasQnn = $true + $deviceInfo.message += " | QNN runtime detected at: $path" + break + } +} + +# Check environment variables for Snapdragon SDK +if (Test-Path env:QUALCOMM_SDK_ROOT) { + $deviceInfo.hasQnn = $true + $deviceInfo.message += " | QUALCOMM_SDK_ROOT found" +} + +if (Test-Path env:QNN_SDK_ROOT) { + $deviceInfo.hasQnn = $true + $deviceInfo.message += " | QNN_SDK_ROOT found" +} + +# Output result +if ($Json) { + Write-Output (ConvertTo-Json $deviceInfo -AsArray) +} +else { + if ($deviceInfo.isSnapdragon) { + Write-Output "✓ Snapdragon device detected: $($deviceInfo.processorName)" + if ($deviceInfo.hasQnn) { + Write-Output "✓ QNN runtime available - hardware acceleration enabled" + } + else { + Write-Output "ℹ QNN runtime not detected - install Qualcomm AI Hub or Snapdragon SDK for acceleration" + } + } + else { + Write-Output "ℹ ARM64 device detected but not confirmed as Snapdragon" + Write-Output " Processor: $($deviceInfo.processorName)" + } +} + +exit 0 diff --git a/windows-installer/installer.nsi b/windows-installer/installer.nsi new file mode 100644 index 00000000..f2a56c47 --- /dev/null +++ b/windows-installer/installer.nsi @@ -0,0 +1,132 @@ +; Edge Impulse Linux CLI – Windows Installer + +Unicode true +SetCompressor /SOLID lzma + +!ifndef PRODUCT_VERSION + !define PRODUCT_VERSION "0.0.0" +!endif +!ifndef ARCH + !define ARCH "x64" +!endif + +!define PRODUCT_NAME "Edge Impulse Linux CLI" +!define PRODUCT_PUBLISHER "EdgeImpulse Inc." +!define PRODUCT_URL "https://edgeimpulse.com" +!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\EdgeImpulseLinuxCLI" +!define STAGING_DIR "staging" +!define OUTPUT_DIR "output" +!define BRAND_HEADER_BMP "branding\header.bmp" +!define BRAND_WELCOME_BMP "branding\welcome.bmp" + +!include "MUI2.nsh" +!include "x64.nsh" +!include "WinMessages.nsh" +!include "FileFunc.nsh" + +!define MUI_ABORTWARNING +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_RIGHT +!define MUI_HEADERIMAGE_BITMAP "${BRAND_HEADER_BMP}" +!define MUI_WELCOMEFINISHPAGE_BITMAP "${BRAND_WELCOME_BMP}" + +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "${STAGING_DIR}\LICENSE.txt" +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_LANGUAGE "English" + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +OutFile "${OUTPUT_DIR}\edge-impulse-linux-cli-windows-${ARCH}-setup.exe" +InstallDir "$PROGRAMFILES64\EdgeImpulse Linux CLI" +InstallDirRegKey HKLM "${UNINSTALL_KEY}" "InstallLocation" +RequestExecutionLevel admin +ShowInstDetails show +ShowUnInstDetails show + +VIProductVersion "${PRODUCT_VERSION}.0" +VIAddVersionKey "ProductName" "${PRODUCT_NAME}" +VIAddVersionKey "CompanyName" "${PRODUCT_PUBLISHER}" +VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}" +VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}" +VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Installer" + +Section "Edge Impulse Linux CLI (required)" SecMain + SectionIn RO + + SetOutPath "$INSTDIR" + File "${STAGING_DIR}\node.exe" + File "${STAGING_DIR}\LICENSE.txt" + File "${STAGING_DIR}\package.json" + File /r "${STAGING_DIR}\build" + File /r "${STAGING_DIR}\node_modules" + + SetOutPath "$INSTDIR\bin" + File "${STAGING_DIR}\bin\edge-impulse-linux.cmd" + File "${STAGING_DIR}\bin\edge-impulse-linux-runner.cmd" + File "${STAGING_DIR}\bin\edge-impulse-camera-debug.cmd" + File "${STAGING_DIR}\bin\detect-snapdragon.ps1" + + FileOpen $R0 "$TEMP\_ei_addpath.ps1" w + FileWrite $R0 "$$binDir = '$INSTDIR\bin'$\r$\n" + FileWrite $R0 "$$key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'$\r$\n" + FileWrite $R0 "$$cur = (Get-ItemProperty -Path $$key -Name Path).Path$\r$\n" + FileWrite $R0 "$$parts = $$cur -split ';' | Where-Object { $$_ -ne '' }$\r$\n" + FileWrite $R0 "if ($$parts -notcontains $$binDir) {$\r$\n" + FileWrite $R0 " Set-ItemProperty -Path $$key -Name Path -Value (($$parts + $$binDir) -join ';')$\r$\n" + FileWrite $R0 "}$\r$\n" + FileClose $R0 + + nsExec::ExecToLog "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $\"$TEMP\_ei_addpath.ps1$\"" + Pop $R1 + Delete "$TEMP\_ei_addpath.ps1" + + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + WriteRegStr HKLM "${UNINSTALL_KEY}" "DisplayName" "${PRODUCT_NAME}" + WriteRegStr HKLM "${UNINSTALL_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegStr HKLM "${UNINSTALL_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" + WriteRegStr HKLM "${UNINSTALL_KEY}" "URLInfoAbout" "${PRODUCT_URL}" + WriteRegStr HKLM "${UNINSTALL_KEY}" "InstallLocation" "$INSTDIR" + WriteRegStr HKLM "${UNINSTALL_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegDWORD HKLM "${UNINSTALL_KEY}" "NoModify" 1 + WriteRegDWORD HKLM "${UNINSTALL_KEY}" "NoRepair" 1 + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINSTALL_KEY}" "EstimatedSize" "$0" + + WriteUninstaller "$INSTDIR\uninstall.exe" +SectionEnd + +Section "Uninstall" + FileOpen $R0 "$TEMP\_ei_rmpath.ps1" w + FileWrite $R0 "$$binDir = '$INSTDIR\bin'$\r$\n" + FileWrite $R0 "$$key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'$\r$\n" + FileWrite $R0 "$$cur = (Get-ItemProperty -Path $$key -Name Path).Path$\r$\n" + FileWrite $R0 "$$parts = $$cur -split ';' | Where-Object { $$_ -ne '' -and $$_ -ne $$binDir }$\r$\n" + FileWrite $R0 "Set-ItemProperty -Path $$key -Name Path -Value ($$parts -join ';')$\r$\n" + FileClose $R0 + + nsExec::ExecToLog "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $\"$TEMP\_ei_rmpath.ps1$\"" + Pop $R1 + Delete "$TEMP\_ei_rmpath.ps1" + + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + RMDir /r "$INSTDIR\build" + RMDir /r "$INSTDIR\node_modules" + RMDir /r "$INSTDIR\bin" + Delete "$INSTDIR\node.exe" + Delete "$INSTDIR\LICENSE.txt" + Delete "$INSTDIR\package.json" + Delete "$INSTDIR\uninstall.exe" + RMDir "$INSTDIR" + + DeleteRegKey HKLM "${UNINSTALL_KEY}" +SectionEnd diff --git a/windows-installer/prepare-branding.ps1 b/windows-installer/prepare-branding.ps1 new file mode 100644 index 00000000..5bebfed7 --- /dev/null +++ b/windows-installer/prepare-branding.ps1 @@ -0,0 +1,49 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = Resolve-Path "$PSScriptRoot\.." +$sourceImage = Join-Path $repoRoot "img\edge-impulse-logo.png" +# Fall back to the monorepo logo if running from within the edgeimpulse monorepo checkout +if (-not (Test-Path $sourceImage)) { + $sourceImage = Join-Path $repoRoot "studio\public\themes\default\png\logo.png" +} +$brandingDir = Join-Path $PSScriptRoot "branding" +$headerBmp = Join-Path $brandingDir "header.bmp" +$welcomeBmp = Join-Path $brandingDir "welcome.bmp" + +if (-not (Test-Path $sourceImage)) { + throw "Branding source image not found: $sourceImage" +} + +New-Item -ItemType Directory -Force -Path $brandingDir | Out-Null + +$magick = Get-Command magick -ErrorAction SilentlyContinue +if (-not $magick) { + throw "ImageMagick (magick) is required to generate NSIS branding bitmaps" +} + +Write-Host "Generating NSIS branding bitmaps from $sourceImage" + +# NSIS header image: 150x57 +& $magick.Source "$sourceImage" ` + -background white -gravity center ` + -resize 150x57 ` + -extent 150x57 ` + BMP3:"$headerBmp" + +# NSIS welcome/finish side image: 164x314 +& $magick.Source "$sourceImage" ` + -background white -gravity center ` + -resize 164x314 ` + -extent 164x314 ` + BMP3:"$welcomeBmp" + +if (-not (Test-Path $headerBmp) -or -not (Test-Path $welcomeBmp)) { + throw "Failed to generate branding assets" +} + +Write-Host "Branding assets generated:" +Get-ChildItem $brandingDir | Format-Table Name, Length -AutoSize diff --git a/windows-installer/stage.ps1 b/windows-installer/stage.ps1 new file mode 100644 index 00000000..51f36089 --- /dev/null +++ b/windows-installer/stage.ps1 @@ -0,0 +1,131 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidateSet('x64', 'arm64')] + [string]$Arch, + + [Parameter(Mandatory)] + [string]$NodeVersion +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repoRoot = Resolve-Path "$PSScriptRoot\.." +$stagingDir = Join-Path $PSScriptRoot "staging" +$outputDir = Join-Path $PSScriptRoot "output" + +Write-Host "=========================================================" +Write-Host " Staging Edge Impulse Linux CLI Windows installer" +Write-Host " Arch : $Arch" +Write-Host " Node.js : $NodeVersion" +Write-Host " Staging dir: $stagingDir" +Write-Host "=========================================================" + +foreach ($dir in @($stagingDir, $outputDir, + "$stagingDir\bin", + "$stagingDir\build", + "$stagingDir\node_modules")) { + if (-not (Test-Path $dir)) { + New-Item -ItemType Directory -Path $dir | Out-Null + } +} + +$nodeZipName = "node-v${NodeVersion}-win-${Arch}.zip" +$nodeZipUrl = "https://nodejs.org/dist/v${NodeVersion}/${nodeZipName}" +$nodeZipPath = Join-Path $Env:TEMP $nodeZipName +$nodeExeDest = Join-Path $stagingDir "node.exe" + +if (-not (Test-Path $nodeExeDest)) { + Write-Host "`n--> Downloading $nodeZipUrl" + Invoke-WebRequest -Uri $nodeZipUrl -OutFile $nodeZipPath -UseBasicParsing + + Write-Host "--> Extracting node.exe" + $expandDir = Join-Path $Env:TEMP "node-expand-$Arch" + Expand-Archive -Path $nodeZipPath -DestinationPath $expandDir -Force + + $extractedExe = Get-ChildItem -Path $expandDir -Filter "node.exe" -Recurse | Select-Object -First 1 + if (-not $extractedExe) { + throw "node.exe not found in Node.js archive" + } + Copy-Item -Path $extractedExe.FullName -Destination $nodeExeDest -Force + + Remove-Item $nodeZipPath -Force -ErrorAction SilentlyContinue + Remove-Item $expandDir -Recurse -Force -ErrorAction SilentlyContinue +} + +Write-Host "`n--> Copying build/" +$buildSrc = Join-Path $repoRoot "build" +if (-not (Test-Path $buildSrc)) { throw "build/ not found. Run npm run build first." } +robocopy $buildSrc "$stagingDir\build" /E /NFL /NDL /NJH /NJS | Out-Null +if ($LASTEXITCODE -gt 7) { throw "robocopy build failed with exit code $LASTEXITCODE" } + +Write-Host "--> Copying node_modules/" +$nmSrc = Join-Path $repoRoot "node_modules" +if (-not (Test-Path $nmSrc)) { throw "node_modules/ not found. Run npm ci first." } +robocopy $nmSrc "$stagingDir\node_modules" /E /NFL /NDL /NJH /NJS | Out-Null +if ($LASTEXITCODE -gt 7) { throw "robocopy node_modules failed with exit code $LASTEXITCODE" } + +# Detect Snapdragon hardware acceleration support (ARM64 only) +Write-Host "`n--> Checking Snapdragon hardware acceleration support" +if ($Arch -eq 'arm64') { + $detectScriptPath = Join-Path $PSScriptRoot "detect-snapdragon.ps1" + if (Test-Path $detectScriptPath) { + & $detectScriptPath + } + else { + Write-Host " (Snapdragon detection script not found)" + } +} +else { + Write-Host " Skipped (x64 architecture)" +} + +Write-Host "`n--> Writing .cmd shims" +$binEntries = @{ + 'edge-impulse-linux' = 'build\cli\linux\linux.js' + 'edge-impulse-linux-runner' = 'build\cli\linux\runner.js' + 'edge-impulse-camera-debug' = 'build\cli\linux\camera-debug.js' +} +foreach ($name in $binEntries.Keys) { + $jsPath = $binEntries[$name] + $cmdPath = Join-Path "$stagingDir\bin" "${name}.cmd" + $content = @" +@echo off +"%~dp0..\node.exe" "%~dp0..\$jsPath" %* +"@ + Set-Content -Path $cmdPath -Value $content -Encoding ASCII + Write-Host " $name.cmd" +} + +Write-Host "`n--> Copying Snapdragon detection helper" +$detectScript = Join-Path $PSScriptRoot "detect-snapdragon.ps1" +if (Test-Path $detectScript) { + Copy-Item -Path $detectScript -Destination "$stagingDir\bin\detect-snapdragon.ps1" -Force + Write-Host " detect-snapdragon.ps1 (for ARM64 optimization)" +} +else { + Write-Host " (Snapdragon detection script not found)" +} + +Write-Host "`n--> Copying LICENSE" +$licenseSrc = Join-Path $repoRoot "LICENSE.3-clause-bsd-clear" +if (Test-Path $licenseSrc) { + Copy-Item -Path $licenseSrc -Destination "$stagingDir\LICENSE.txt" -Force +} +else { + Set-Content -Path "$stagingDir\LICENSE.txt" -Value "BSD-3-Clause-Clear" -Encoding UTF8 +} + +Write-Host "--> Copying package.json" +$packageJsonSrc = Join-Path $repoRoot "package.json" +if (-not (Test-Path $packageJsonSrc)) { throw "package.json not found at $packageJsonSrc" } +Copy-Item -Path $packageJsonSrc -Destination "$stagingDir\package.json" -Force + +Write-Host "`n=========================================================" +Write-Host " Staging complete." +Write-Host " Files in $stagingDir :" +Get-ChildItem $stagingDir | Format-Table Name, Length -AutoSize +Write-Host "=========================================================" + +exit 0