Skip to content

Build Electron and NWJS packages #2010

Build Electron and NWJS packages

Build Electron and NWJS packages #2010

# Workflow to build Electron and (if not a packaged app) NWJS packages
name: Build Electron and NWJS packages
on:
schedule:
# Nightly run at 03:39 UTC
- cron: '39 03 * * *'
workflow_dispatch:
inputs:
version:
description: Specific version to build like v9.9.9 without suffix (if empty, builds version in package.json)
required: false
default: ''
target:
type: choice
description: Do you wish to build for "release", "nightly", or "artefacts" for testing? Nightly will only publish on the main branch. Artefacts will appear under the workflow run. For release, a draft release with corresponding tag must exist.
required: false
options:
- release
- nightly
- artefacts
default: 'artefacts'
build_platform:
description: Which platform(s) to build for?
type: choice
required: false
options:
- all
- linux
- macos
- windows
- win7
- win11
default: 'all'
win11packages:
description: For Windows 11 builds, select which packages to build
type: choice
required: false
options:
- all
- nsisweb
- appx
default: 'all'
sign:
description: Do you wish to sign the Windows packages?
type: choice
required: false
options:
- true
- false
default: 'false'
publish_to:
description: Publish to GitHub releases or AWS S3?
type: choice
required: false
options:
- github
- aws
default: 'github'
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
INPUT_TARGET: ${{ github.event.inputs.target }}
CRON_LAUNCHED: ${{ github.event.schedule }}
INPUT_SIGN: ${{ github.event.inputs.sign || 'false' }}
BUILD_PLATFORM: ${{ github.event.inputs.build_platform || 'all' }}
WIN11_PACKAGES: ${{ github.event.inputs.win11packages }}
PUBLISH_TO: ${{ github.event.inputs.publish_to }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ESIGNER_USERNAME: ${{ secrets.ESIGNER_USER_USERNAME }}
ESIGNER_PASSWORD: ${{ secrets.ESIGNER_USER_PASSWORD }}
ESIGNER_TOTP_SECRET: ${{ secrets.ESIGNER_USER_TOTP_SECRET }}
MASTER_KEY_FILE: "C:\\Users\\runneradmin\\eSignerCKA\\master.key"
INSTALL_DIR: C:\Users\runneradmin\eSignerCKA
SSH_KEY: ${{ secrets.KIWIXJSPWA_FILE_UPLOAD_KEY }}
REF_NAME: ${{ github.ref_name }}
jobs:
Release_Linux:
if: github.event.inputs.build_platform == 'all' || github.event.inputs.build_platform == 'linux' || github.event.inputs.build_platform == ''
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install dependencies
run: npm install
- name: Rewrite app version number and file name
run: |
chmod +x ./scripts/rewrite_app_version_number.sh
./scripts/rewrite_app_version_number.sh
# Replace -app in archive name for Electron apps
sed -i -E 's/(mdwiki[^-]+)-app_/\1_/g' ./www/js/init.js
- name: Build production code
run: npm run build-min
- name: Download archive if needed
run: |
echo "Changing to the dist directory"
cd dist && pwd
# Get archive name
packagedFile=$(grep -m1 'params\[.packagedFile' www/js/init.js | sed -E "s/^.+'([^']+\.zim)'.+/\1/")
# If packagedFile doesn't match a zim file, we don't need to download anything, so exit
if [[ ! $packagedFile =~ \.zim$ ]]; then
echo -e "\nNo zim file to download.\n"
exit 0
fi
# If file doesn't exist in FS, download it
if [ ! -f "archives/$packagedFile" ]; then
# Generalize the name if cron_launched and download it
if [[ $CRON_LAUNCHED = true ]]; then
packagedFileGeneric=$(sed -E 's/_[0-9-]+(\.zim)/\1/' <<<"$packagedFile")
echo -e "\nDownloading https://download.kiwix.org/zim/$packagedFileGeneric"
wget -nv "https://download.kiwix.org/zim/$packagedFileGeneric" -O "archives/$packagedFile"
else
flavour=$(sed -E 's/^([^_]+)_.+$/\1/' <<<"$packagedFile")
if [[ $flavour = "mdwiki" ]]; then
flavour='other'
fi
echo -e "\nDownloading https://mirror.download.kiwix.org/zim/$flavour/$packagedFile"
wget -nv "https://mirror.download.kiwix.org/zim/$flavour/$packagedFile" -O "archives/$packagedFile"
fi
fi
ls archives
if [ -f "archives/$packagedFile" ]; then
echo -e "\nFile $packagedFile now available in 'archives'.\n"
else
echo -e "\nError! We could not obtain the requested archive $packagedFile!\n"
exit 1
fi
- name: Configure publish target (GitHub or AWS S3)
run: |
if [[ "$PUBLISH_TO" == "aws" ]]; then
echo "Configuring electron-builder to publish to AWS S3..."
if [[ -n "$INPUT_VERSION" ]]; then
VERSION=$(echo "$INPUT_VERSION" | sed -E 's/^v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
echo "Extracted version from INPUT_VERSION: $VERSION"
else
echo "No INPUT_VERSION provided, reading version from init.js..."
VERSION=$(grep 'params\[.appVersion' www/js/init.js | sed -E "s/[^[:digit:]]+([^\"']+).*/\1/" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
echo "Extracted version from init.js: $VERSION"
fi
if [[ -z "$VERSION" ]]; then
echo "ERROR: Could not determine version number"
exit 1
fi
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const s3Config = {
provider: 's3',
bucket: 'org-kiwix-download.branded-apps',
endpoint: 'https://s3.eu-west-2.wasabisys.com',
path: 'WikiMed/v${VERSION}/',
acl: 'public-read'
};
if (pkg.build.linux) pkg.build.linux.publish = [s3Config];
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
console.log('S3 path configured as:', s3Config.path);
"
echo "Builds will upload to: s3://org-kiwix-download.branded-apps/WikiMed/v${VERSION}/"
else
echo "Using default GitHub publish configuration"
fi
- name: Build and publish 64bit
env:
USE_HARD_LINKS: false
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
run: |
# echo "Setting the module type to one supported by Electron in ./package.json"
# sed -i -E 's/("type":\s+)"module"/\1"commonjs"/' ./package.json
echo "Copying package.json to dist"
cp ./package.json ./dist/package.json
echo "Installing dependencies in dist"
cd dist && npm install && cd ..
PUBLISH_FLAG="never"
if [[ "$PUBLISH_TO" == "aws" ]]; then
PUBLISH_FLAG="always"
fi
echo "Building 64bit packages for ref_name=$REF_NAME..."
if [[ $REF_NAME = "main" ]]; then
npx electron-builder --linux AppImage:x64 AppImage:arm64 deb:x64 rpm:x64 --publish $PUBLISH_FLAG --projectDir dist
else
npx electron-builder --linux AppImage:x64 AppImage:arm64 deb:x64 --publish $PUBLISH_FLAG --projectDir dist
fi
- name: Build and publish 32bit
env:
USE_HARD_LINKS: false
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
run: |
echo "Changing Electron version to latest that supports 32bit Linux (18.3.15) in ./dist/package.json"
sed -i -E 's/("electron":\s")[^"]+/\118.3.15/' ./dist/package.json
echo "Installing dependencies in dist"
cd dist && npm install && cd ..
PUBLISH_FLAG="never"
if [[ "$PUBLISH_TO" == "aws" ]]; then
PUBLISH_FLAG="always"
fi
echo "Building 32bit packages for ref_name=$REF_NAME..."
if [[ $REF_NAME = "main" ]]; then
npx electron-builder --linux AppImage:ia32 deb:ia32 rpm:ia32 --publish $PUBLISH_FLAG --projectDir dist
else
npx electron-builder --linux AppImage:ia32 --publish $PUBLISH_FLAG --projectDir dist
fi
- name: Upload packages to Kiwix
if: github.ref_name == 'main' && github.event.inputs.target != 'artefacts' && github.event.inputs.publish_to != 'aws'
run: |
echo "$SSH_KEY" > ./scripts/ssh_key
chmod 600 ./scripts/ssh_key
chmod +x ./scripts/publish_linux_packages_to_kiwix.sh
./scripts/publish_linux_packages_to_kiwix.sh
- name: Archive build artefacts
if: github.event.inputs.target == 'artefacts'
uses: actions/upload-artifact@v4
with:
name: kiwix-js-electron_linux
path: |
dist/bld/Electron/*.AppImage
dist/bld/Electron/*.deb
dist/bld/Electron/*.rpm
Release_macOS:
if: github.event.inputs.build_platform == 'all' || github.event.inputs.build_platform == 'macos' || github.event.inputs.build_platform == ''
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install dependencies
run: npm install
- name: Rewrite app version number and file name
run: |
chmod +x ./scripts/rewrite_app_version_number.sh
./scripts/rewrite_app_version_number.sh
# Replace -app in archive name for Electron apps (BSD sed syntax for macOS)
sed -i '' -E 's/(mdwiki[^-]+)-app_/\1_/g' ./www/js/init.js
- name: Build production code
run: npm run build-min
- name: Download archive if needed
run: |
echo "Changing to the dist directory"
cd dist && pwd
# Get archive name
packagedFile=$(grep -m1 'params\[.packagedFile' www/js/init.js | sed -E "s/^.+'([^']+\.zim)'.+/\1/")
# If packagedFile doesn't match a zim file, we don't need to download anything, so exit
if [[ ! $packagedFile =~ \.zim$ ]]; then
echo -e "\nNo zim file to download.\n"
exit 0
fi
# If file doesn't exist in FS, download it
if [ ! -f "archives/$packagedFile" ]; then
# Generalize the name if cron_launched and download it
if [[ $CRON_LAUNCHED = true ]]; then
packagedFileGeneric=$(sed -E 's/_[0-9-]+(\.zim)/\1/' <<<"$packagedFile")
echo -e "\nDownloading https://download.kiwix.org/zim/$packagedFileGeneric"
wget -nv "https://download.kiwix.org/zim/$packagedFileGeneric" -O "archives/$packagedFile"
else
flavour=$(sed -E 's/^([^_]+)_.+$/\1/' <<<"$packagedFile")
if [[ $flavour = "mdwiki" ]]; then
flavour='other'
fi
echo -e "\nDownloading https://mirror.download.kiwix.org/zim/$flavour/$packagedFile"
wget -nv "https://mirror.download.kiwix.org/zim/$flavour/$packagedFile" -O "archives/$packagedFile"
fi
fi
ls archives
if [ -f "archives/$packagedFile" ]; then
echo -e "\nFile $packagedFile now available in 'archives'.\n"
else
echo -e "\nError! We could not obtain the requested archive $packagedFile!\n"
exit 1
fi
- name: Configure publish target (GitHub or AWS S3)
run: |
if [[ "$PUBLISH_TO" == "aws" ]]; then
echo "Configuring electron-builder to publish to AWS S3..."
if [[ -n "$INPUT_VERSION" ]]; then
VERSION=$(echo "$INPUT_VERSION" | sed -E 's/^v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
echo "Extracted version from INPUT_VERSION: $VERSION"
else
echo "No INPUT_VERSION provided, reading version from init.js..."
VERSION=$(grep 'params\[.appVersion' www/js/init.js | sed -E "s/[^[:digit:]]+([^\"']+).*/\1/" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
echo "Extracted version from init.js: $VERSION"
fi
if [[ -z "$VERSION" ]]; then
echo "ERROR: Could not determine version number"
exit 1
fi
echo "S3_VERSION=$VERSION" >> $GITHUB_ENV
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const s3Config = {
provider: 's3',
bucket: 'org-kiwix-download.branded-apps',
endpoint: 'https://s3.eu-west-2.wasabisys.com',
path: 'WikiMed/v${VERSION}/',
acl: 'public-read'
};
if (pkg.build.mac) pkg.build.mac.publish = [s3Config];
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2));
"
echo "S3 publish configuration injected into package.json"
echo "Builds will upload to: s3://org-kiwix-download.branded-apps/WikiMed/v${VERSION}/"
else
echo "Using default GitHub publish configuration"
fi
- name: Build macOS packages (High Sierra/Mojave compatible)
env:
USE_HARD_LINKS: false
run: |
# Save the original Electron version (just the version string, no quotes)
ORIGINAL_ELECTRON_VERSION=$(grep -m1 '"electron":' ./package.json | sed -E 's/.*"electron"[^"]*"([^"]+)".*/\1/')
echo "Original Electron version: $ORIGINAL_ELECTRON_VERSION"
echo "ORIGINAL_ELECTRON_VERSION=$ORIGINAL_ELECTRON_VERSION" >> $GITHUB_ENV
echo "Changing Electron version to exactly 26.6.10 (last to support macOS 10.13+) in ./package.json"
sed -i '' -E 's/"electron"[[:space:]]*:[[:space:]]*"[^"]+"/"electron": "26.6.10"/' ./package.json
echo "Verifying package.json has exact version (no ^ or ~):"
grep '"electron":' ./package.json
echo "Copying the package.json to dist"
cp ./package.json ./dist/package.json
cp ./package-lock.json ./dist/package-lock.json
echo "Removing old Electron and installing exactly 26.6.10 in root and dist"
npm uninstall electron && npm install electron@26.6.10 --save-exact --save-dev
cd dist && npm install electron@26.6.10 --save-exact --save-dev && cd ..
echo "Verifying installed Electron version..."
npx electron --version
echo "Building macOS packages for High Sierra/Mojave..."
if [[ $REF_NAME = "main" ]]; then
npx electron-builder --mac zip:x64 --publish never --projectDir dist
else
npx electron-builder --mac zip:x64 --publish never --projectDir dist
fi
echo "Renaming High Sierra package..."
cd dist/bld/Electron
for file in *.zip; do
if [[ -f "$file" ]] && [[ "$file" =~ -mac\.zip$ ]]; then
newname="${file/-mac.zip/-macOS-HighSierra.zip}"
mv "$file" "$newname"
echo "Renamed High Sierra build: $file -> $newname"
if [[ -f "${file}.blockmap" ]]; then
mv "${file}.blockmap" "${newname}.blockmap"
fi
fi
done
# echo "Removing blockmap files (autoupdate not supported for renamed packages)..."
# rm -f *.blockmap
cd ../../..
echo "Restoring original Electron version: $ORIGINAL_ELECTRON_VERSION"
sed -i '' -E "s/\"electron\"[[:space:]]*:[[:space:]]*\"[^\"]+\"/\"electron\": \"$ORIGINAL_ELECTRON_VERSION\"/" ./package.json
echo "Verifying restoration:"
grep '"electron":' ./package.json
- name: Build macOS packages (modern)
env:
USE_HARD_LINKS: false
run: |
echo "Reinstalling dependencies with modern Electron version in root and dist"
cp ./package.json ./dist/package.json
cp ./package-lock.json ./dist/package-lock.json
npm install
cd dist && npm uninstall electron && npm install electron --save-dev && cd ..
echo "Verifying Electron versions before building..."
echo "Checking package.json electron entry:"
grep '"electron":' ./package.json
echo "Root Electron version:"
npx electron --version
echo "Dist Electron version:"
cd dist && npx electron --version && cd ..
EXPECTED_VERSION=$(grep -m1 '"electron":' ./package.json | sed -E 's/.*"electron"[^"]*"([^"]+)".*/\1/' | sed -E 's/^[\^~]//')
echo "DEBUG: Extracted EXPECTED_VERSION='$EXPECTED_VERSION'"
ACTUAL_VERSION=$(cd dist && npx electron --version | sed 's/^v//')
echo "Expected: $EXPECTED_VERSION, Actual in dist: $ACTUAL_VERSION"
if [[ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]]; then
echo "ERROR: Electron version mismatch! Expected $EXPECTED_VERSION but got $ACTUAL_VERSION"
else
echo "✓ Electron versions verified successfully"
fi
echo "Building modern macOS packages for ref_name=$REF_NAME..."
npx electron-builder --mac zip:x64 zip:arm64 --publish never --projectDir dist
echo "Renaming modern macOS packages to avoid conflicts..."
cd dist/bld/Electron
for file in *.zip; do
if [[ -f "$file" ]]; then
newname=""
if [[ "$file" =~ -arm64-mac\.zip$ ]]; then
# Rename the ARM64 build
newname="${file/-arm64-mac.zip/-macOS-arm64.zip}"
elif [[ "$file" =~ -mac\.zip$ ]]; then
# This is the x64 modern build, rename it to indicate architecture
newname="${file/-mac.zip/-macOS-x64.zip}"
fi
if [[ -n "$newname" ]]; then
mv "$file" "$newname"
echo "Renamed build: $file -> $newname"
if [[ -f "${file}.blockmap" ]]; then
mv "${file}.blockmap" "${newname}.blockmap"
fi
fi
fi
done
# echo "Removing blockmap files (autoupdate not supported for renamed packages)..."
# rm -f *.blockmap
cd ../../..
- name: Smoke test Electron app
run: |
echo "Extracting macOS app for testing..."
cd dist/bld/Electron
# Find the first zip file (prefer x64 if available)
ZIP_FILE=$(ls -1 *.zip | head -1)
echo "Found zip file: $ZIP_FILE"
# Extract the zip file
unzip -q "$ZIP_FILE"
# Find the .app bundle
APP_BUNDLE=$(find . -name "*.app" -type d | head -1)
echo "Found app bundle: $APP_BUNDLE"
if [ -z "$APP_BUNDLE" ]; then
echo "Error: No .app bundle found!"
exit 1
fi
# Remove quarantine attribute (required for CI)
xattr -dr com.apple.quarantine "$APP_BUNDLE" || true
# Test basic app launch (verify it can start without crashing)
echo "Testing app launch..."
# Determine the executable name based on the app bundle name
APP_NAME=$(basename "$APP_BUNDLE" .app)
EXECUTABLE_PATH="$APP_BUNDLE/Contents/MacOS/$APP_NAME"
echo "Using executable: $EXECUTABLE_PATH"
# Verify executable exists
if [ ! -f "$EXECUTABLE_PATH" ]; then
echo "Error: Executable not found at $EXECUTABLE_PATH"
ls -la "$APP_BUNDLE/Contents/MacOS/"
exit 1
fi
"$EXECUTABLE_PATH" \
--disable-gpu-sandbox \
--no-sandbox \
--disable-web-security \
--disable-dev-shm-usage \
--no-first-run \
--disable-default-apps &
APP_PID=$!
echo "Started app with PID: $APP_PID"
# Wait for app to start and check if process is running
sleep 8
if kill -0 $APP_PID 2>/dev/null; then
echo "✅ App process is running successfully"
# Check if app is responsive by looking at process details
echo "Process details:"
ps -p $APP_PID -o pid,ppid,time,command || true
echo "App is running and responsive"
# Kill app gracefully
kill -TERM $APP_PID 2>/dev/null || true
sleep 3
# Force kill if still running
if kill -0 $APP_PID 2>/dev/null; then
echo "Force killing app..."
kill -KILL $APP_PID 2>/dev/null || true
fi
echo "✅ App launched and terminated successfully"
else
echo "❌ App process failed to start or crashed immediately"
exit 1
fi
echo "Smoke test completed successfully"
- name: Upload macOS builds to S3
if: env.PUBLISH_TO == 'aws'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
run: |
which aws || pip3 install awscli --quiet
ENDPOINT="https://s3.eu-west-2.wasabisys.com"
S3_PATH="s3://org-kiwix-download.branded-apps/WikiMed/v${S3_VERSION}"
echo "Uploading macOS builds to $S3_PATH ..."
for file in dist/bld/Electron/*.zip; do
if [[ -f "$file" ]]; then
filename=$(basename "$file")
echo "Uploading $filename..."
aws s3 cp "$file" "$S3_PATH/$filename" --endpoint-url "$ENDPOINT" --no-progress
fi
done
echo "macOS upload complete."
- name: Publish packages to GitHub
if: github.event.inputs.target == 'release' && github.event.inputs.publish_to != 'aws'
run: |
chmod +x ./scripts/publish_macos_packages_to_github.sh
./scripts/publish_macos_packages_to_github.sh
- name: Upload packages to Kiwix
if: github.ref_name == 'main' && github.event.inputs.target != 'artefacts' && github.event.inputs.publish_to != 'aws'
run: |
echo "$SSH_KEY" > ./scripts/ssh_key
chmod 600 ./scripts/ssh_key
chmod +x ./scripts/publish_macos_packages_to_kiwix.sh
./scripts/publish_macos_packages_to_kiwix.sh
- name: Archive build artefacts
if: github.event.inputs.target == 'artefacts'
uses: actions/upload-artifact@v4
with:
name: kiwix-js-electron_macos
path: |
dist/bld/Electron/*.zip
Release_Windows:
if: github.event.inputs.build_platform == 'all' || github.event.inputs.build_platform == 'windows' || github.event.inputs.build_platform == 'win7' || github.event.inputs.build_platform == 'win11' || github.event.inputs.build_platform == ''
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Find and set signtool.exe path
if: github.event.inputs.sign == 'true'
shell: powershell
run: |
# Find the latest available Windows SDK version with signtool
$sdkPath = "C:\Program Files (x86)\Windows Kits\10\bin"
if (Test-Path $sdkPath) {
$latestSdk = Get-ChildItem $sdkPath -Directory |
Where-Object { $_.Name -match '^\d+\.\d+\.\d+\.\d+$' } |
Sort-Object Name -Descending |
Select-Object -First 1
if ($latestSdk) {
$signtoolPath = Join-Path $latestSdk.FullName "x86\signtool.exe"
if (Test-Path $signtoolPath) {
echo "Found signtool at: $signtoolPath"
echo "SIGNTOOL_PATH=$signtoolPath" >> $env:GITHUB_ENV
} else {
# Try x64 version
$signtoolPath = Join-Path $latestSdk.FullName "x64\signtool.exe"
if (Test-Path $signtoolPath) {
echo "Found signtool at: $signtoolPath"
echo "SIGNTOOL_PATH=$signtoolPath" >> $env:GITHUB_ENV
} else {
echo "ERROR: signtool.exe not found in SDK version $($latestSdk.Name)"
exit 1
}
}
} else {
echo "ERROR: No Windows SDK found at $sdkPath"
exit 1
}
} else {
echo "ERROR: Windows SDK path does not exist: $sdkPath"
exit 1
}
- name: Install dependencies
run: npm install
- name: Rewrite app version number and file name
run: |
$INPUT_VERSION = $Env:INPUT_VERSION
$INPUT_TARGET = $Env:INPUT_TARGET
$CRON_LAUNCHED = $Env:CRON_LAUNCHED
./scripts/Rewrite-AppVersion.ps1
# Replace -app in archive name for Electron apps
(Get-Content ./www/js/init.js) -replace '(mdwiki[^-]+)-app_', '$1_' | Set-Content -encoding 'utf8BOM' ./www/js/init.js
- name: Build production code
run: npm run build-min
- name: Download archive if needed
run: |
echo "Changing to the dist directory"
cd dist && pwd
$packagedFile = (Select-String 'packagedFile' "www\js\init.js" -List) -ireplace "^.+'([^']+\.zim)'.+", '$1'
# If packagedFile doesn't match a zim file, we don't need to download anything, so exit
if ($packagedFile -and ! ($packagedFile -match '\.zim$')) {
Write-Host "`nNo zim file to download.`n"
exit 0
}
if ($packagedFile -and ! (Test-Path "archives\$packagedFile" -PathType Leaf)) {
# File not in archives, so generalize the name (if nightly) and download it
if ($CRON_LAUNCHED) {
$packagedFileGeneric = $packagedFile -replace '_[0-9-]+(\.zim)', '$1'
Write-Host "`nDownloading https://download.kiwix.org/zim/$packagedFileGeneric"
Invoke-WebRequest "https://download.kiwix.org/zim/$packagedFileGeneric" -OutFile "archives\$packagedFile"
} else {
$flavour = $packagedFile -replace '^([^_]+)_.+$', '$1'
if ($flavour -eq 'mdwiki') {
$flavour = 'other'
}
Write-Host "`nDownloading https://mirror.download.kiwix.org/zim/$flavour/$packagedFile"
Invoke-WebRequest "https://mirror.download.kiwix.org/zim/$flavour/$packagedFile" -OutFile "archives\$packagedFile"
}
}
ls archives
if ($packagedFile -and (Test-Path "archives\$packagedFile" -PathType Leaf)) {
Write-Host "`nFile $packagedFile now available in 'archives'.`n" -ForegroundColor Green
} else {
Write-Host "`nError! We could not obtain the requested archive $packagedFile!`n" -ForegroundColor Red
exit 1
}
- name: Install and configure eSigner CKA and Windows SDK
if: github.event.inputs.sign == 'true'
env:
ESIGNER_URL: https://github.com/SSLcom/eSignerCKA/releases/download/v1.0.7/SSL.COM-eSigner-CKA_1.0.7.zip
run: |
Set-StrictMode -Version 'Latest'
# Download and Unzip eSignerCKA Setup
Invoke-WebRequest -OutFile eSigner_CKA_Setup.zip "$env:ESIGNER_URL"
Expand-Archive -Force eSigner_CKA_Setup.zip
Remove-Item eSigner_CKA_Setup.zip
Move-Item -Destination “eSigner_CKA_Installer.exe” -Path “eSigner_CKA_*\*.exe”
# Install eSignerCKA
New-Item -ItemType Directory -Force -Path ${{ env.INSTALL_DIR }}
./eSigner_CKA_Installer.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR=”${{ env.INSTALL_DIR }}” | Out-Null
# Disable logger
$LogConfig = Get-Content -Path ${{ env.INSTALL_DIR }}/log4net.config
$LogConfig[0] = '<log4net threshold="OFF">'
$LogConfig | Set-Content -Path ${{ env.INSTALL_DIR }}/log4net.config
# Configure
${{ env.INSTALL_DIR }}/eSignerCKATool.exe config -mode product -user "${{ env.ESIGNER_USERNAME }}" -pass "${{ env.ESIGNER_PASSWORD }}" -totp "${{ env.ESIGNER_TOTP_SECRET }}" -key "${{ env.MASTER_KEY_FILE }}" -r
${{ env.INSTALL_DIR }}/eSignerCKATool.exe unload
${{ env.INSTALL_DIR }}/eSignerCKATool.exe load
# Find certificate
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
echo Certificate: $CodeSigningCert
# Extract thumbprint and subject name
$Thumbprint = $CodeSigningCert.Thumbprint
$SubjectName = ($CodeSigningCert.Subject -replace ", ?", "`n" | ConvertFrom-StringData).CN
ls -l ${{ env.MASTER_KEY_FILE }}
echo "ED_SIGNTOOL_THUMBPRINT=$Thumbprint" >> $env:GITHUB_ENV
echo "ED_SIGNTOOL_SUBJECT_NAME=$SubjectName" >> $env:GITHUB_ENV
- name: Configure publish target (GitHub or AWS S3)
shell: powershell
run: |
$PUBLISH_TO = $Env:PUBLISH_TO
$INPUT_VERSION = $Env:INPUT_VERSION
if ($PUBLISH_TO -eq 'aws') {
echo "Configuring electron-builder to publish to AWS S3..."
$VERSION = $INPUT_VERSION -replace '^v([0-9.]+).*', '$1'
$packageJson = Get-Content -Raw ./package.json | ConvertFrom-Json
# Replace GitHub publish config with S3 config in win, mac, and linux sections
$s3Config = @{
provider = "s3"
bucket = "org-kiwix-download.branded-apps"
endpoint = "https://s3.eu-west-2.wasabisys.com"
path = "WikiMed/v$VERSION/"
acl = "public-read"
}
$packageJson.build.win.publish = @($s3Config)
if ($packageJson.build.PSObject.Properties.Name -contains 'mac') {
$packageJson.build.mac.publish = @($s3Config)
}
if ($packageJson.build.PSObject.Properties.Name -contains 'linux') {
$packageJson.build.linux.publish = @($s3Config)
}
# Configure nsis-web appPackageUrl to point to S3
$s3BaseUrl = "https://s3.eu-west-2.wasabisys.com/org-kiwix-download.branded-apps/WikiMed/v$VERSION"
if ($packageJson.build.PSObject.Properties.Name -contains 'nsisWeb') {
$packageJson.build.PSObject.Properties.Remove('nsisWeb')
}
$packageJson.build | Add-Member -MemberType NoteProperty -Name 'nsisWeb' -Value ([PSCustomObject]@{
appPackageUrl = $s3BaseUrl
})
$packageJson | ConvertTo-Json -Depth 100 | Set-Content ./package.json
echo "S3 publish configuration injected into package.json"
echo "nsis-web appPackageUrl set to: $s3BaseUrl"
echo "Builds will upload to: s3://org-kiwix-download.branded-apps/WikiMed/v$VERSION/"
} else {
echo "Using default GitHub publish configuration"
}
- name: Run electron builder for Win 7/8/8.1
if: github.event.inputs.build_platform != 'win11'
shell: powershell
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
run: |
$GITHUB_TOKEN = $Env:GITHUB_TOKEN
$INPUT_VERSION = $Env:INPUT_VERSION
$INPUT_TARGET = $Env:INPUT_TARGET
$CRON_LAUNCHED = $Env:CRON_LAUNCHED
$INPUT_SIGN = !$CRON_LAUNCHED -and [System.Convert]::ToBoolean($Env:INPUT_SIGN)
if (-not ($Env:CRON_LAUNCHED -or ($Env:INPUT_TARGET -eq 'nightly'))) {
$INPUT_VERSION_E = $INPUT_VERSION -replace '^v([0-9.]+).*', '$1-E'
} else {
$INPUT_VERSION_E = $INPUT_VERSION -replace '^v', ''
}
./scripts/Rewrite-DraftReleaseTag.ps1
$ORIGINAL_ELECTRON_VERSION = (Get-Content ./package.json | sls '"electron":') -replace '.*"electron"\s*:\s*"(.*)".*', '$1'
echo "ORIGINAL_ELECTRON_VERSION=$ORIGINAL_ELECTRON_VERSION" | Out-File $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Setting the Electron version to the latest supporting Windows 7/8/8.1 (=22.3.25)"
(Get-Content ./package.json) -replace '("electron":\s+)"[\d.]+[\w\d-.]*?"', '$1"22.3.25"' | Set-Content ./package.json
echo "Copying the package.json to dist"
cp ./package.json ./dist/package.json
cp ./package-lock.json ./dist/package-lock.json
echo "Installing dependencies in root and dist"
npm install; cd dist; npm install; cd ..
echo "Installed Electron version:$(npx electron --version)"
# Check if this is WikiMed (NSIS Setup too large to sign)
$packageName = (Get-Content ./package.json | ConvertFrom-Json).name
$isWikiMed = $packageName -match 'wikimed'
if ($isWikiMed) {
# WikiMed: build unpacked dir + zip (NSIS Setup exe too large for signtool)
echo "Building Windows 7+ unpacked app (no NSIS installer - package too large for signing)..."
if ($INPUT_SIGN) {
npx electron-builder build --win dir:ia32 --publish never --projectDir dist
} else {
echo "********************"
echo "*** DEV: Packages are NOT signed, as requested! ***"
echo "********************"
npx electron-builder build --config ../scripts/electronBuilder.cjs --win dir:ia32 --publish never --projectDir dist
}
# Create portable zip with launcher scripts
$unpacked = "dist/bld/Electron/win-ia32-unpacked"
$foldername = "kiwix-js-pwa-win32-ia32"
$productName = (Get-Content ./package.json | ConvertFrom-Json).productName
$zipDir = "dist/bld/Electron/$foldername"
if (Test-Path $zipDir) { rm -r $zipDir }
mkdir $zipDir
cp -r "$unpacked\*" $zipDir
# Create launcher scripts
$executable = (ls "$unpacked/*.exe" | Select-Object -First 1).Name
$launcherStub = "dist/bld/Electron/Start $productName"
$batch = "@cd `"$foldername`"`r`n@start `"$productName`" `"$executable`"`r`n"
$batch > "$launcherStub.bat"
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$launcherStub.lnk")
$Shortcut.TargetPath = '%windir%\explorer.exe'
$Shortcut.Arguments = "$foldername\$executable"
$Shortcut.IconLocation = '%windir%\explorer.exe,12'
$Shortcut.Save()
$safeName = $productName -replace '\s', '-'
$zipName = "$safeName-Win7-$INPUT_VERSION_E.zip"
$zipPath = "dist/bld/Electron/$zipName"
echo "Compressing to $zipPath..."
# Use 7z instead of Compress-Archive (which has a 2GB stream limit)
7z a -tzip $zipPath "$launcherStub.bat" "$launcherStub.lnk" $zipDir
# Upload to S3 if configured
if ($Env:PUBLISH_TO -eq 'aws') {
$VERSION = $Env:INPUT_VERSION -replace '^v([0-9.]+).*', '$1'
$s3Path = "s3://org-kiwix-download.branded-apps/WikiMed/v$VERSION/$zipName"
echo "Uploading $zipName to S3..."
aws s3 cp $zipPath $s3Path --endpoint-url "https://s3.eu-west-2.wasabisys.com" --no-progress
}
ls ./dist/bld/Electron/
if ($Env:INPUT_TARGET -ne 'artefacts' -and $Env:PUBLISH_TO -ne 'aws') {
& ./scripts/Publish-ElectronPackages.ps1 -githubonly -portableonly
}
} else {
# Non-WikiMed: build standard NSIS Setup installer
echo "Building Windows 7+ 32bit NSIS package..."
if ($INPUT_SIGN) {
npm run dist-win-nsis
} else {
echo "********************"
echo "*** DEV: Build will appear to sign, but packages are NOT actually signed, as requested! ***"
echo "********************"
npm run dist-win-nsis-skipsigning
}
echo "Renaming Windows 7+ executable"
$files = @("Kiwix JS Electron", "WikiMed by Kiwix", "Wikivoyage by Kiwix")
foreach ($file in $files) {
mv "dist/bld/Electron/$file Setup*.exe" "dist/bld/Electron/$file Win7 Setup $INPUT_VERSION_E.exe"
mv "dist/bld/Electron/$file Setup*.exe.blockmap" "dist/bld/Electron/$file Win7 Setup $INPUT_VERSION_E.exe.blockmap"
}
mv "dist/bld/Electron/latest.yml" "dist/bld/Electron/latest-win7.yml"
ls ./dist/bld/Electron/
if ($INPUT_TARGET -ne 'artefacts') {
& ./scripts/Publish-ElectronPackages.ps1 -githubonly
}
}
./scripts/Rewrite-DraftReleaseTag.ps1
- name: Run electron builder for Win 10/11
if: github.event.inputs.build_platform != 'win7'
shell: powershell
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
run: |
$GITHUB_TOKEN = $Env:GITHUB_TOKEN
$INPUT_VERSION = $Env:INPUT_VERSION
$INPUT_TARGET = $Env:INPUT_TARGET
$CRON_LAUNCHED = $Env:CRON_LAUNCHED
$INPUT_SIGN = !$CRON_LAUNCHED -and [System.Convert]::ToBoolean($Env:INPUT_SIGN)
$ORIGINAL_ELECTRON_VERSION = $Env:ORIGINAL_ELECTRON_VERSION
if (![string]::IsNullOrEmpty($ORIGINAL_ELECTRON_VERSION)) {
echo "Restoring original Electron version: $ORIGINAL_ELECTRON_VERSION"
(Get-Content ./package.json) -replace '("electron":\s+)"[\d.]+[\w\d-.]*?"', ('$1"' + $ORIGINAL_ELECTRON_VERSION + '"') | Set-Content ./package.json
} else {
echo "Using Electron version defined in package.json"
}
./scripts/Rewrite-DraftReleaseTag.ps1
# Set the module type to one supported by Electron
# (Get-Content ./package.json) -replace '("type":\s+)"module"', '$1"commonjs"' | Set-Content ./package.json
echo "Copying the rewritten package.json to dist"
cp ./package.json ./dist/package.json
cp ./package-lock.json ./dist/package-lock.json
echo "Installing dependencies in root and dist"
npm install; cd dist; npm install; cd ..
echo "Installed Electron version:$(npx electron --version)"
$PUBLISH_TO = $Env:PUBLISH_TO
$WIN11_PACKAGES = $Env:WIN11_PACKAGES
echo "Building Windows packages (sign=$INPUT_SIGN, publish_to=$PUBLISH_TO, packages=$WIN11_PACKAGES)..."
if (!$INPUT_SIGN) {
echo "********************"
echo "*** DEV: Packages are NOT signed, as requested! ***"
echo "********************"
}
# Determine phase 1 build targets based on app type
# WikiMed: skip NSIS Setup (too large for signtool) and portable (too large to self-extract)
# Wikivoyage: skip portable (too large to self-extract) but keep NSIS Setup
# Kiwix JS Electron: build all targets
$packageName = (Get-Content ./package.json | ConvertFrom-Json).name
$phase1Targets = @()
if ($packageName -notmatch 'wikimed') { $phase1Targets += 'NSIS:ia32' }
if ($packageName -notmatch 'wikimed|wikivoyage') { $phase1Targets += 'portable:ia32' }
$phase1Targets += 'appx'
$PHASE1_TARGETS = $phase1Targets -join ' '
echo "Phase 1 build targets: $PHASE1_TARGETS"
# Build and publish to configured target (GitHub or S3)
# The publish target was configured in package.json by the previous step
# For AWS publishing, use --publish always to override the onTagOrDraft policy
if ($PUBLISH_TO -eq 'aws') {
# AWS S3 publishing - use --publish always
switch ($WIN11_PACKAGES) {
'nsisweb' {
if (!$INPUT_SIGN) {
npx electron-builder build --config ../scripts/electronBuilder.cjs --win nsis-web --ia32 --x64 --arm64 --publish always --projectDir dist
} else {
npx electron-builder build --win nsis-web --ia32 --x64 --arm64 --publish always --projectDir dist
}
}
'appx' {
if (!$INPUT_SIGN) {
npx electron-builder build --config ../scripts/electronBuilder.cjs --win appx --publish always --projectDir dist
} else {
npx electron-builder build --win appx --publish always --projectDir dist
}
}
default {
if (!$INPUT_SIGN) {
npx electron-builder build --config ../scripts/electronBuilder.cjs --win $PHASE1_TARGETS --publish always --projectDir dist
node scripts/delete-latest-yml.js
npx electron-builder build --config ../scripts/electronBuilder.cjs --win nsis-web --ia32 --x64 --arm64 --publish always --projectDir dist
} else {
npx electron-builder build --win $PHASE1_TARGETS --publish always --projectDir dist
node scripts/delete-latest-yml.js
npx electron-builder build --win nsis-web --ia32 --x64 --arm64 --publish always --projectDir dist
}
}
}
} else {
# GitHub publishing - use default onTagOrDraft policy
switch ($WIN11_PACKAGES) {
'nsisweb' {
if (!$INPUT_SIGN) {
npx electron-builder build --config ../scripts/electronBuilder.cjs --win nsis-web --ia32 --x64 --arm64 --projectDir dist
} else {
npx electron-builder build --win nsis-web --ia32 --x64 --arm64 --projectDir dist
}
}
'appx' {
if (!$INPUT_SIGN) {
npx electron-builder build --config ../scripts/electronBuilder.cjs --win appx --projectDir dist
} else {
npx electron-builder build --win appx --projectDir dist
}
}
default {
if (!$INPUT_SIGN) {
npx electron-builder build --config ../scripts/electronBuilder.cjs --win $PHASE1_TARGETS --projectDir dist
node scripts/delete-latest-yml.js
npx electron-builder build --config ../scripts/electronBuilder.cjs --win nsis-web --ia32 --x64 --arm64 --projectDir dist
} else {
npx electron-builder build --win $PHASE1_TARGETS --projectDir dist
node scripts/delete-latest-yml.js
npx electron-builder build --win nsis-web --ia32 --x64 --arm64 --projectDir dist
}
}
}
}
./scripts/Rewrite-DraftReleaseTag.ps1
- name: Build portable Electron app
if: github.event.inputs.build_platform != 'win7' && github.event.inputs.win11packages != 'nsisweb' && github.event.inputs.win11packages != 'appx'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_KEY }}
run: |
if (-not ($Env:CRON_LAUNCHED -or ($Env:INPUT_TARGET -eq 'nightly'))) {
$GITHUB_TOKEN = $Env:GITHUB_TOKEN
$INPUT_VERSION_E = $Env:INPUT_VERSION -replace '^(v[0-9.]+).*', '$1E'
if ($Env:INPUT_VERSION -match '-Wiki[\w]+') {
$INPUT_VERSION_E += $matches[0]
}
# To ensure there is enough disk space, we can delete the archive that is no longer needed
rm -r dist/archives
./scripts/Create-DraftRelease -buildonly -tag_name $INPUT_VERSION_E -portableonly -nobundle -wingetprompt N -nobranchcheck
# Upload portable zip to S3 if configured
if ($Env:PUBLISH_TO -eq 'aws') {
$VERSION = $Env:INPUT_VERSION -replace '^v([0-9.]+).*', '$1'
$s3Base = "s3://org-kiwix-download.branded-apps/WikiMed/v$VERSION"
$zipFiles = Get-ChildItem "dist/bld/Electron/*.zip"
foreach ($zip in $zipFiles) {
# Skip the Win7 zip (already uploaded in the Win7 step)
if ($zip.Name -notmatch 'Win7') {
echo "Uploading $($zip.Name) to S3..."
aws s3 cp $zip.FullName "$s3Base/$($zip.Name)" --endpoint-url "https://s3.eu-west-2.wasabisys.com" --no-progress
}
}
}
}
- name: Publish packages
if: github.event.inputs.build_platform != 'win7' && github.event.inputs.target != 'artefacts' && github.event.inputs.publish_to != 'aws'
run: |
$SSH_KEY = $Env:SSH_KEY
$keyfile = ".\scripts\ssh_key"
# Write SSH key with Unix line endings (LF only, no BOM)
[System.IO.File]::WriteAllText($keyfile, $SSH_KEY + "`n", [System.Text.UTF8Encoding]::new($false))
$GITHUB_TOKEN = $Env:GITHUB_TOKEN
$INPUT_VERSION = $Env:INPUT_VERSION
$INPUT_TARGET = $Env:INPUT_TARGET
$CRON_LAUNCHED = $Env:CRON_LAUNCHED
if ($Env:REF_NAME -eq "main") {
./scripts/Publish-ElectronPackages.ps1 -portableonly
} else {
echo "DEV: Note that only nightly builds launched from main will be published to the Kiwix server..."
./scripts/Publish-ElectronPackages.ps1 -portableonly -githubonly
}
- name: Archive build artefacts
if: github.event.inputs.target == 'artefacts'
uses: actions/upload-artifact@v4
with:
name: kiwix-js-electron_windows
path: |
dist/bld/Electron/*.exe
dist/bld/Electron/*.appx
dist/bld/Electron/*.zip
dist/bld/Electron/nsis-web/*.exe
dist/bld/Electron/nsis-web/*.nsis.7z
Release_NWJS:
if: github.ref_name == 'main' && (github.event.inputs.build_platform == 'all' || github.event.inputs.build_platform == '')
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install dependencies
run: npm install
- name: Build production code
run: npm run build-min
- name: Select NWJS app
run: |
ren package.json package.json.electron
ren package.json.nwjs package.json
- name: Rewrite app version number
run: |
$INPUT_VERSION = $Env:INPUT_VERSION
$INPUT_TARGET = $Env:INPUT_TARGET
$CRON_LAUNCHED = $Env:CRON_LAUNCHED
./scripts/Rewrite-AppVersion.ps1
cp package.json dist/package.json
- name: Download archive if needed
run: |
echo "Changing to the dist directory"
cd dist && pwd
$packagedFile = (Select-String 'packagedFile' "www\js\init.js" -List) -ireplace "^.+'([^']+\.zim)'.+", '$1'
# If packagedFile doesn't match a zim file, we don't need to download anything, so exit
if ($packagedFile -and ! ($packagedFile -match '\.zim$')) {
Write-Host "`nNo zim file to download.`n"
exit 0
}
if ($packagedFile -and ! (Test-Path "archives\$packagedFile" -PathType Leaf)) {
# File not in archives, so generalize the name (if nightly) and download it
if ($CRON_LAUNCHED) {
$packagedFileGeneric = $packagedFile -replace '_[0-9-]+(\.zim)', '$1'
Write-Host "`nDownloading https://download.kiwix.org/zim/$packagedFileGeneric"
Invoke-WebRequest "https://download.kiwix.org/zim/$packagedFileGeneric" -OutFile "archives\$packagedFile"
} else {
$flavour = $packagedFile -replace '^([^_]+)_.+$', '$1'
if ($flavour -eq 'mdwiki') {
$flavour = 'other'
}
Write-Host "`nDownloading https://mirror.download.kiwix.org/zim/$flavour/$packagedFile"
Invoke-WebRequest "https://mirror.download.kiwix.org/zim/$flavour/$packagedFile" -OutFile "archives\$packagedFile"
}
}
ls archives
if ($packagedFile -and (Test-Path "archives\$packagedFile" -PathType Leaf)) {
Write-Host "`nFile $packagedFile now available in 'archives'.`n" -ForegroundColor Green
} else {
Write-Host "`nError! We could not obtain the requested archive $packagedFile!`n" -ForegroundColor Red
exit 1
}
- name: Build NWJS app
run: ./scripts/Build-NWJS.ps1
- name: Publish
if: github.event.inputs.target != 'artefacts'
run: |
$SSH_KEY = $Env:SSH_KEY
$keyfile = ".\scripts\ssh_key"
# Write SSH key with Unix line endings (LF only, no BOM)
[System.IO.File]::WriteAllText($keyfile, $SSH_KEY + "`n", [System.Text.UTF8Encoding]::new($false))
$GITHUB_TOKEN = $Env:GITHUB_TOKEN
$INPUT_VERSION = $Env:INPUT_VERSION
$INPUT_TARGET = $Env:INPUT_TARGET
$CRON_LAUNCHED = $Env:CRON_LAUNCHED
if ($Env:REF_NAME -eq "main") {
./scripts/Publish-ElectronPackages.ps1
} else {
echo "DEV: Note that this is a manual test build from branch $Env:REF_NAME"
./scripts/Publish-ElectronPackages.ps1
}
- name: Archive build artefacts
if: github.event.inputs.target == 'artefacts'
uses: actions/upload-artifact@v4
with:
name: kiwix-js-nwjs_windows
path: dist/bld/nwjs/*.zip