Skip to content

Commit 7a08b10

Browse files
committed
Add Windows offline installer workflow for linux CLI package
1 parent b679f7f commit 7a08b10

4 files changed

Lines changed: 336 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: Build Windows Linux-CLI Installer
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
upload_release:
7+
description: 'Upload as a GitHub Release (requires a version tag)'
8+
type: boolean
9+
default: false
10+
push:
11+
branches:
12+
- windows-offline-installer
13+
tags:
14+
- 'linux-cli-v*'
15+
16+
jobs:
17+
build:
18+
name: Build installer (${{ matrix.arch }})
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
include:
23+
- arch: x64
24+
runner: windows-latest
25+
- arch: arm64
26+
runner: windows-11-arm
27+
runs-on: ${{ matrix.runner }}
28+
continue-on-error: ${{ matrix.arch == 'arm64' }}
29+
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- name: Set up Node.js
34+
uses: actions/setup-node@v4
35+
with:
36+
node-version: '22'
37+
architecture: ${{ matrix.arch }}
38+
39+
- name: Install dependencies and compile TypeScript
40+
shell: pwsh
41+
run: |
42+
npm ci
43+
npm run build
44+
45+
- name: Stage installer bundle
46+
shell: pwsh
47+
run: |
48+
& windows-installer/stage.ps1 `
49+
-Arch "${{ matrix.arch }}" `
50+
-NodeVersion "22.15.1"
51+
if ($LASTEXITCODE -gt 1) { exit $LASTEXITCODE }
52+
exit 0
53+
54+
- name: Install NSIS
55+
shell: pwsh
56+
run: choco install nsis --no-progress -y
57+
58+
- name: Build NSIS installer
59+
shell: pwsh
60+
run: |
61+
$version = (Get-Content package.json | ConvertFrom-Json).version
62+
Set-Location windows-installer
63+
New-Item -ItemType Directory -Force output | Out-Null
64+
65+
$makeNsis = $null
66+
$candidates = @(
67+
"$env:ChocolateyInstall\bin\makensis.exe",
68+
"$env:ProgramData\chocolatey\bin\makensis.exe",
69+
"$env:ProgramFiles\NSIS\makensis.exe",
70+
"C:\Program Files (x86)\NSIS\makensis.exe"
71+
)
72+
73+
foreach ($candidate in $candidates) {
74+
if (Test-Path $candidate) {
75+
$makeNsis = $candidate
76+
break
77+
}
78+
}
79+
80+
if (-not $makeNsis) {
81+
$cmd = Get-Command makensis -ErrorAction SilentlyContinue
82+
if ($cmd) { $makeNsis = $cmd.Source }
83+
}
84+
85+
if (-not $makeNsis) {
86+
throw "makensis.exe not found after NSIS install"
87+
}
88+
89+
& $makeNsis /DARCH=${{ matrix.arch }} /DPRODUCT_VERSION=$version installer.nsi
90+
91+
- name: Upload installer artifact
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: edge-impulse-linux-cli-windows-${{ matrix.arch }}
95+
path: windows-installer/output/edge-impulse-linux-cli-windows-${{ matrix.arch }}-setup.exe
96+
if-no-files-found: error
97+
98+
- name: Upload to GitHub Release
99+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') || (github.event_name == 'workflow_dispatch' && inputs.upload_release)
100+
uses: softprops/action-gh-release@v2
101+
with:
102+
files: windows-installer/output/edge-impulse-linux-cli-windows-${{ matrix.arch }}-setup.exe
103+
tag_name: ${{ github.ref_name }}

windows-installer/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
staging/
2+
output/

windows-installer/installer.nsi

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
; Edge Impulse Linux CLI – Windows Installer
2+
3+
Unicode true
4+
SetCompressor /SOLID lzma
5+
6+
!ifndef PRODUCT_VERSION
7+
!define PRODUCT_VERSION "0.0.0"
8+
!endif
9+
!ifndef ARCH
10+
!define ARCH "x64"
11+
!endif
12+
13+
!define PRODUCT_NAME "Edge Impulse Linux CLI"
14+
!define PRODUCT_PUBLISHER "EdgeImpulse Inc."
15+
!define PRODUCT_URL "https://edgeimpulse.com"
16+
!define UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\EdgeImpulseLinuxCLI"
17+
!define STAGING_DIR "staging"
18+
!define OUTPUT_DIR "output"
19+
20+
!include "MUI2.nsh"
21+
!include "x64.nsh"
22+
!include "WinMessages.nsh"
23+
!include "FileFunc.nsh"
24+
25+
!define MUI_ABORTWARNING
26+
27+
!insertmacro MUI_PAGE_WELCOME
28+
!insertmacro MUI_PAGE_LICENSE "${STAGING_DIR}\LICENSE.txt"
29+
!insertmacro MUI_PAGE_DIRECTORY
30+
!insertmacro MUI_PAGE_INSTFILES
31+
!insertmacro MUI_PAGE_FINISH
32+
33+
!insertmacro MUI_UNPAGE_CONFIRM
34+
!insertmacro MUI_UNPAGE_INSTFILES
35+
36+
!insertmacro MUI_LANGUAGE "English"
37+
38+
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
39+
OutFile "${OUTPUT_DIR}\edge-impulse-linux-cli-windows-${ARCH}-setup.exe"
40+
InstallDir "$PROGRAMFILES64\EdgeImpulse Linux CLI"
41+
InstallDirRegKey HKLM "${UNINSTALL_KEY}" "InstallLocation"
42+
RequestExecutionLevel admin
43+
ShowInstDetails show
44+
ShowUnInstDetails show
45+
46+
VIProductVersion "${PRODUCT_VERSION}.0"
47+
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
48+
VIAddVersionKey "CompanyName" "${PRODUCT_PUBLISHER}"
49+
VIAddVersionKey "FileVersion" "${PRODUCT_VERSION}"
50+
VIAddVersionKey "ProductVersion" "${PRODUCT_VERSION}"
51+
VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Installer"
52+
53+
Section "Edge Impulse Linux CLI (required)" SecMain
54+
SectionIn RO
55+
56+
SetOutPath "$INSTDIR"
57+
File "${STAGING_DIR}\node.exe"
58+
File "${STAGING_DIR}\LICENSE.txt"
59+
File "${STAGING_DIR}\package.json"
60+
File /r "${STAGING_DIR}\build"
61+
File /r "${STAGING_DIR}\node_modules"
62+
63+
SetOutPath "$INSTDIR\bin"
64+
File "${STAGING_DIR}\bin\edge-impulse-linux.cmd"
65+
File "${STAGING_DIR}\bin\edge-impulse-linux-runner.cmd"
66+
File "${STAGING_DIR}\bin\edge-impulse-camera-debug.cmd"
67+
68+
FileOpen $R0 "$TEMP\_ei_addpath.ps1" w
69+
FileWrite $R0 "$$binDir = '$INSTDIR\bin'$\r$\n"
70+
FileWrite $R0 "$$key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'$\r$\n"
71+
FileWrite $R0 "$$cur = (Get-ItemProperty -Path $$key -Name Path).Path$\r$\n"
72+
FileWrite $R0 "$$parts = $$cur -split ';' | Where-Object { $$_ -ne '' }$\r$\n"
73+
FileWrite $R0 "if ($$parts -notcontains $$binDir) {$\r$\n"
74+
FileWrite $R0 " Set-ItemProperty -Path $$key -Name Path -Value (($$parts + $$binDir) -join ';')$\r$\n"
75+
FileWrite $R0 "}$\r$\n"
76+
FileClose $R0
77+
78+
nsExec::ExecToLog "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $\"$TEMP\_ei_addpath.ps1$\""
79+
Pop $R1
80+
Delete "$TEMP\_ei_addpath.ps1"
81+
82+
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
83+
84+
WriteRegStr HKLM "${UNINSTALL_KEY}" "DisplayName" "${PRODUCT_NAME}"
85+
WriteRegStr HKLM "${UNINSTALL_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
86+
WriteRegStr HKLM "${UNINSTALL_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
87+
WriteRegStr HKLM "${UNINSTALL_KEY}" "URLInfoAbout" "${PRODUCT_URL}"
88+
WriteRegStr HKLM "${UNINSTALL_KEY}" "InstallLocation" "$INSTDIR"
89+
WriteRegStr HKLM "${UNINSTALL_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
90+
WriteRegDWORD HKLM "${UNINSTALL_KEY}" "NoModify" 1
91+
WriteRegDWORD HKLM "${UNINSTALL_KEY}" "NoRepair" 1
92+
93+
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
94+
IntFmt $0 "0x%08X" $0
95+
WriteRegDWORD HKLM "${UNINSTALL_KEY}" "EstimatedSize" "$0"
96+
97+
WriteUninstaller "$INSTDIR\uninstall.exe"
98+
SectionEnd
99+
100+
Section "Uninstall"
101+
FileOpen $R0 "$TEMP\_ei_rmpath.ps1" w
102+
FileWrite $R0 "$$binDir = '$INSTDIR\bin'$\r$\n"
103+
FileWrite $R0 "$$key = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'$\r$\n"
104+
FileWrite $R0 "$$cur = (Get-ItemProperty -Path $$key -Name Path).Path$\r$\n"
105+
FileWrite $R0 "$$parts = $$cur -split ';' | Where-Object { $$_ -ne '' -and $$_ -ne $$binDir }$\r$\n"
106+
FileWrite $R0 "Set-ItemProperty -Path $$key -Name Path -Value ($$parts -join ';')$\r$\n"
107+
FileClose $R0
108+
109+
nsExec::ExecToLog "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $\"$TEMP\_ei_rmpath.ps1$\""
110+
Pop $R1
111+
Delete "$TEMP\_ei_rmpath.ps1"
112+
113+
SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
114+
115+
RMDir /r "$INSTDIR\build"
116+
RMDir /r "$INSTDIR\node_modules"
117+
RMDir /r "$INSTDIR\bin"
118+
Delete "$INSTDIR\node.exe"
119+
Delete "$INSTDIR\LICENSE.txt"
120+
Delete "$INSTDIR\package.json"
121+
Delete "$INSTDIR\uninstall.exe"
122+
RMDir "$INSTDIR"
123+
124+
DeleteRegKey HKLM "${UNINSTALL_KEY}"
125+
SectionEnd

windows-installer/stage.ps1

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
[CmdletBinding()]
2+
param(
3+
[Parameter(Mandatory)]
4+
[ValidateSet('x64', 'arm64')]
5+
[string]$Arch,
6+
7+
[Parameter(Mandatory)]
8+
[string]$NodeVersion
9+
)
10+
11+
Set-StrictMode -Version Latest
12+
$ErrorActionPreference = 'Stop'
13+
14+
$repoRoot = Resolve-Path "$PSScriptRoot\.."
15+
$stagingDir = Join-Path $PSScriptRoot "staging"
16+
$outputDir = Join-Path $PSScriptRoot "output"
17+
18+
Write-Host "========================================================="
19+
Write-Host " Staging Edge Impulse Linux CLI Windows installer"
20+
Write-Host " Arch : $Arch"
21+
Write-Host " Node.js : $NodeVersion"
22+
Write-Host " Staging dir: $stagingDir"
23+
Write-Host "========================================================="
24+
25+
foreach ($dir in @($stagingDir, $outputDir,
26+
"$stagingDir\bin",
27+
"$stagingDir\build",
28+
"$stagingDir\node_modules")) {
29+
if (-not (Test-Path $dir)) {
30+
New-Item -ItemType Directory -Path $dir | Out-Null
31+
}
32+
}
33+
34+
$nodeZipName = "node-v${NodeVersion}-win-${Arch}.zip"
35+
$nodeZipUrl = "https://nodejs.org/dist/v${NodeVersion}/${nodeZipName}"
36+
$nodeZipPath = Join-Path $Env:TEMP $nodeZipName
37+
$nodeExeDest = Join-Path $stagingDir "node.exe"
38+
39+
if (-not (Test-Path $nodeExeDest)) {
40+
Write-Host "`n--> Downloading $nodeZipUrl"
41+
Invoke-WebRequest -Uri $nodeZipUrl -OutFile $nodeZipPath -UseBasicParsing
42+
43+
Write-Host "--> Extracting node.exe"
44+
$expandDir = Join-Path $Env:TEMP "node-expand-$Arch"
45+
Expand-Archive -Path $nodeZipPath -DestinationPath $expandDir -Force
46+
47+
$extractedExe = Get-ChildItem -Path $expandDir -Filter "node.exe" -Recurse | Select-Object -First 1
48+
if (-not $extractedExe) {
49+
throw "node.exe not found in Node.js archive"
50+
}
51+
Copy-Item -Path $extractedExe.FullName -Destination $nodeExeDest -Force
52+
53+
Remove-Item $nodeZipPath -Force -ErrorAction SilentlyContinue
54+
Remove-Item $expandDir -Recurse -Force -ErrorAction SilentlyContinue
55+
}
56+
57+
Write-Host "`n--> Copying build/"
58+
$buildSrc = Join-Path $repoRoot "build"
59+
if (-not (Test-Path $buildSrc)) { throw "build/ not found. Run npm run build first." }
60+
robocopy $buildSrc "$stagingDir\build" /E /NFL /NDL /NJH /NJS | Out-Null
61+
if ($LASTEXITCODE -gt 7) { throw "robocopy build failed with exit code $LASTEXITCODE" }
62+
63+
Write-Host "--> Copying node_modules/"
64+
$nmSrc = Join-Path $repoRoot "node_modules"
65+
if (-not (Test-Path $nmSrc)) { throw "node_modules/ not found. Run npm ci first." }
66+
robocopy $nmSrc "$stagingDir\node_modules" /E /NFL /NDL /NJH /NJS | Out-Null
67+
if ($LASTEXITCODE -gt 7) { throw "robocopy node_modules failed with exit code $LASTEXITCODE" }
68+
69+
Write-Host "`n--> Writing .cmd shims"
70+
$binEntries = @{
71+
'edge-impulse-linux' = 'build\cli\linux\linux.js'
72+
'edge-impulse-linux-runner' = 'build\cli\linux\runner.js'
73+
'edge-impulse-camera-debug' = 'build\cli\linux\camera-debug.js'
74+
}
75+
foreach ($name in $binEntries.Keys) {
76+
$jsPath = $binEntries[$name]
77+
$cmdPath = Join-Path "$stagingDir\bin" "${name}.cmd"
78+
$content = @"
79+
@echo off
80+
"%~dp0..\node.exe" "%~dp0..\$jsPath" %*
81+
"@
82+
Set-Content -Path $cmdPath -Value $content -Encoding ASCII
83+
Write-Host " $name.cmd"
84+
}
85+
86+
Write-Host "`n--> Copying LICENSE"
87+
$licenseSrc = Join-Path $repoRoot "LICENSE.3-clause-bsd-clear"
88+
if (Test-Path $licenseSrc) {
89+
Copy-Item -Path $licenseSrc -Destination "$stagingDir\LICENSE.txt" -Force
90+
}
91+
else {
92+
Set-Content -Path "$stagingDir\LICENSE.txt" -Value "BSD-3-Clause-Clear" -Encoding UTF8
93+
}
94+
95+
Write-Host "--> Copying package.json"
96+
$packageJsonSrc = Join-Path $repoRoot "package.json"
97+
if (-not (Test-Path $packageJsonSrc)) { throw "package.json not found at $packageJsonSrc" }
98+
Copy-Item -Path $packageJsonSrc -Destination "$stagingDir\package.json" -Force
99+
100+
Write-Host "`n========================================================="
101+
Write-Host " Staging complete."
102+
Write-Host " Files in $stagingDir :"
103+
Get-ChildItem $stagingDir | Format-Table Name, Length -AutoSize
104+
Write-Host "========================================================="
105+
106+
exit 0

0 commit comments

Comments
 (0)