mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(ci): run the release check on built Windows shells (#3346)
This commit is contained in:
1 parent
48a19dedd8
commit
4810eb3e24
4 files changed
+243
-4
No files matched your search
@@ -551,6 +551,16 @@ jobs:
|
|||||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||||
--step verify_windows_unpacked_signatures
|
--step verify_windows_unpacked_signatures
|
||||||
|
|
||||||
|
- name: Verify the packaged shell starts and boots its bundled renderer (Windows)
|
||||||
|
if: matrix.platform == 'windows' && matrix.arch == 'x64'
|
||||||
|
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||||
|
env:
|
||||||
|
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
|
||||||
|
BUILD_VERSION: ${{ env.VERSION }}
|
||||||
|
run: >-
|
||||||
|
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||||
|
--step verify_bundled_renderer_windows
|
||||||
|
|
||||||
- name: Create portable ZIP (Windows)
|
- name: Create portable ZIP (Windows)
|
||||||
if: matrix.platform == 'windows'
|
if: matrix.platform == 'windows'
|
||||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||||
@@ -661,13 +671,59 @@ jobs:
|
|||||||
retention-days: 1
|
retention-days: 1
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
|
|
||||||
|
verify_windows_arm64:
|
||||||
|
name: Verify windows (arm64)
|
||||||
|
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
||||||
|
needs:
|
||||||
|
- meta
|
||||||
|
- build
|
||||||
|
runs-on: windows-11-arm
|
||||||
|
timeout-minutes: 30
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
env:
|
||||||
|
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||||
|
VERSION: ${{ needs.meta.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.meta.outputs.source_sha }}
|
||||||
|
sparse-checkout: fluxer_desktop/scripts
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Download windows arm64 build artifacts
|
||||||
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
pattern: fluxer-desktop-${{ needs.meta.outputs.build_channel }}-windows-arm64*
|
||||||
|
|
||||||
|
- name: Verify the packaged shell starts and boots its bundled renderer
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$zip = Get-ChildItem -Path artifacts -Recurse -Filter "*-$env:VERSION-portable-win-arm64.zip" | Select-Object -First 1
|
||||||
|
if ($null -eq $zip) { Write-Host 'This build has no windows arm64 portable zip'; exit 0 }
|
||||||
|
$app = Join-Path $env:RUNNER_TEMP 'app'
|
||||||
|
Expand-Archive -Path $zip.FullName -DestinationPath $app
|
||||||
|
$exe = Get-ChildItem -Path $app -Recurse -Filter 'Fluxer*.exe' | Where-Object { $_.Name -notlike '*_ExecutionStub.exe' } | Select-Object -First 1
|
||||||
|
if ($null -eq $exe) { throw "$($zip.Name) holds no Fluxer executable" }
|
||||||
|
node fluxer_desktop/scripts/release-check.mjs --app $exe.DirectoryName --channel $env:CHANNEL --expect-renderer $env:VERSION --expect-source bundled --package-origin http://127.0.0.1:9 --app-arg=--disable-gpu --workdir (Join-Path $env:RUNNER_TEMP 'runs') --timeout-seconds 240
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "The windows arm64 build fails its release check" }
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
name: Assemble desktop release assets
|
name: Assemble desktop release assets
|
||||||
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
if: ${{ !cancelled() && needs.build.result == 'success' && needs.verify_windows_arm64.result == 'success' }}
|
||||||
needs:
|
needs:
|
||||||
- meta
|
- meta
|
||||||
- shared_assets
|
- shared_assets
|
||||||
- build
|
- build
|
||||||
|
- verify_windows_arm64
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
environment: desktop-releases
|
environment: desktop-releases
|
||||||
timeout-minutes: 180
|
timeout-minutes: 180
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
name: desktop windows release check
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
channel:
|
||||||
|
description: Release channel
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- canary
|
||||||
|
- stable
|
||||||
|
default: canary
|
||||||
|
version:
|
||||||
|
description: Published version to check
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
expect_failure:
|
||||||
|
description: Pass only when the published build fails to start
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
update_from:
|
||||||
|
description: Comma-separated published versions to install with Setup and update to the version above from the live feed
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- .github/workflows/desktop-windows-release-check.yaml
|
||||||
|
- fluxer_desktop/scripts/release-check.mjs
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: desktop-windows-release-check-${{ github.ref }}-${{ inputs.version || 'pr' }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Check windows (${{ matrix.arch }})
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 45
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: x64
|
||||||
|
os: windows-2025
|
||||||
|
- arch: arm64
|
||||||
|
os: windows-11-arm
|
||||||
|
env:
|
||||||
|
CHANNEL: ${{ inputs.channel || 'canary' }}
|
||||||
|
VERSION: ${{ inputs.version || '2026.1009.20411' }}
|
||||||
|
EXPECT_FAILURE: ${{ github.event_name == 'pull_request' && 'true' || inputs.expect_failure }}
|
||||||
|
UPDATE_FROM: ${{ inputs.update_from || '' }}
|
||||||
|
ARCH: ${{ matrix.arch }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||||
|
with:
|
||||||
|
sparse-checkout: fluxer_desktop/scripts
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Check the portable build
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$work = Join-Path $env:RUNNER_TEMP 'portable'
|
||||||
|
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
||||||
|
$zip = Join-Path $work 'portable.zip'
|
||||||
|
Invoke-WebRequest -Uri "https://pkgs.fluxer.com/desktop/$env:CHANNEL/win32/$env:ARCH/$env:VERSION/portable" -OutFile $zip
|
||||||
|
Expand-Archive -Path $zip -DestinationPath (Join-Path $work 'app')
|
||||||
|
$exe = Get-ChildItem -Path (Join-Path $work 'app') -Recurse -Filter 'Fluxer*.exe' | Where-Object { $_.Name -notlike '*_ExecutionStub.exe' } | Select-Object -First 1
|
||||||
|
if ($null -eq $exe) { throw "The portable build holds no Fluxer executable" }
|
||||||
|
$output = & node fluxer_desktop/scripts/release-check.mjs --app $exe.DirectoryName --channel $env:CHANNEL --expect-renderer $env:VERSION --expect-source bundled --app-arg=--disable-gpu --workdir (Join-Path $work 'runs') --timeout-seconds 240 2>&1
|
||||||
|
$code = $LASTEXITCODE
|
||||||
|
$output | ForEach-Object { Write-Host $_ }
|
||||||
|
if ($env:EXPECT_FAILURE -eq 'true') {
|
||||||
|
if ($code -eq 0) { throw "Expected $env:VERSION to fail its release check, it passed" }
|
||||||
|
Write-Host "$env:VERSION fails its release check on windows $env:ARCH as expected"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
if ($code -ne 0) { throw "The release check failed for $env:VERSION on windows $env:ARCH" }
|
||||||
|
|
||||||
|
- name: Update installed builds from the live feed
|
||||||
|
if: env.UPDATE_FROM != ''
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$channelDirs = @{ canary = 'fluxer_desktop_canary'; stable = 'fluxer_desktop' }
|
||||||
|
$dataDirs = @{ canary = 'fluxercanary'; stable = 'fluxer' }
|
||||||
|
$installRoot = Join-Path $env:LOCALAPPDATA $channelDirs[$env:CHANNEL]
|
||||||
|
$logPath = Join-Path (Join-Path $env:APPDATA $dataDirs[$env:CHANNEL]) 'logs\main.log'
|
||||||
|
$failures = @()
|
||||||
|
foreach ($from in ($env:UPDATE_FROM -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })) {
|
||||||
|
Write-Host "== install $from, then update to $env:VERSION"
|
||||||
|
Get-Process | Where-Object { $_.Path -like "$installRoot*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
if (Test-Path $installRoot) { Remove-Item -Recurse -Force $installRoot }
|
||||||
|
Remove-Item -Recurse -Force (Split-Path $logPath -Parent) -ErrorAction SilentlyContinue
|
||||||
|
$setup = Join-Path $env:RUNNER_TEMP "setup-$from.exe"
|
||||||
|
Invoke-WebRequest -Uri "https://pkgs.fluxer.com/desktop/$env:CHANNEL/win32/$env:ARCH/$from/setup" -OutFile $setup
|
||||||
|
Start-Process -FilePath $setup -ArgumentList '--silent' -Wait
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
Get-Process | Where-Object { $_.Path -like "$installRoot*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
$exe = Get-ChildItem -Path (Join-Path $installRoot 'current') -Filter 'Fluxer*.exe' | Where-Object { $_.Name -notlike '*_ExecutionStub.exe' } | Select-Object -First 1
|
||||||
|
if ($null -eq $exe) { $failures += "$from did not install"; continue }
|
||||||
|
Start-Process -FilePath $exe.FullName -ArgumentList '--disable-gpu'
|
||||||
|
$deadline = (Get-Date).AddMinutes(8)
|
||||||
|
$reported = $null
|
||||||
|
while ((Get-Date) -lt $deadline) {
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
if (Test-Path $logPath) {
|
||||||
|
$line = Select-String -Path $logPath -Pattern "The renderer reported its build: .*Desktop $([regex]::Escape($env:VERSION)), Web $([regex]::Escape($env:VERSION))" | Select-Object -Last 1
|
||||||
|
if ($null -ne $line) { $reported = $line.Line; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$installed = Get-Content (Join-Path $installRoot 'current\sq.version') -Raw -ErrorAction SilentlyContinue
|
||||||
|
Write-Host "installed package after update: $installed"
|
||||||
|
if (Test-Path $logPath) { Get-Content $logPath | Select-String -Pattern 'Serving the renderer|reported its build|NativeModulePreflight|elf-update|shell update|Velopack' | Select-Object -Last 20 | ForEach-Object { Write-Host $_.Line } }
|
||||||
|
if ($null -eq $reported) { $failures += "$from did not reach Desktop and Web $env:VERSION" } else { Write-Host "$from updated: $reported" }
|
||||||
|
Get-Process | Where-Object { $_.Path -like "$installRoot*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if ($failures.Count -gt 0) { throw ($failures -join "`n") }
|
||||||
@@ -146,7 +146,10 @@ function stageApp(options, sandbox) {
|
|||||||
.map((name) => path.join(staged, name))
|
.map((name) => path.join(staged, name))
|
||||||
.find((candidate) => {
|
.find((candidate) => {
|
||||||
const stats = fs.statSync(candidate);
|
const stats = fs.statSync(candidate);
|
||||||
return stats.isFile() && (stats.mode & 0o111) !== 0 && /^fluxer/iu.test(path.basename(candidate));
|
const name = path.basename(candidate);
|
||||||
|
if (!stats.isFile() || !/^fluxer/iu.test(name)) return false;
|
||||||
|
if (process.platform === 'win32') return /\.exe$/iu.test(name) && !/_ExecutionStub\.exe$/iu.test(name);
|
||||||
|
return (stats.mode & 0o111) !== 0;
|
||||||
});
|
});
|
||||||
if (executable == null) fail(`${options.app} holds no fluxer executable`);
|
if (executable == null) fail(`${options.app} holds no fluxer executable`);
|
||||||
return {executable, portableRoot: path.join(staged, 'data')};
|
return {executable, portableRoot: path.join(staged, 'data')};
|
||||||
@@ -265,10 +268,28 @@ async function startUpdateFromRenderer(debugPort, timeoutMs) {
|
|||||||
fail('update: the shell never told the renderer that an update is available');
|
fail('update: the shell never told the renderer that an update is available');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForRenderer({logPath, debugPort, version, timeoutMs, label}) {
|
function startupFailure(logPath) {
|
||||||
|
try {
|
||||||
|
const lines = fs.readFileSync(logPath, 'utf8').split(/\r?\n/u);
|
||||||
|
const index = lines.findIndex((line) => /\[NativeModulePreflight\] Fatal/u.test(line));
|
||||||
|
return index < 0 ? null : lines.slice(index, index + 3).join(' ');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForRenderer({logPath, debugPort, version, timeoutMs, label, child = null}) {
|
||||||
const deadline = Date.now() + timeoutMs;
|
const deadline = Date.now() + timeoutMs;
|
||||||
let observed = {log: observeLog(logPath), page: null};
|
let observed = {log: observeLog(logPath), page: null};
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
|
if (child != null && (child.exitCode != null || child.signalCode != null)) {
|
||||||
|
const failure = startupFailure(logPath);
|
||||||
|
fail(
|
||||||
|
`${label}: the shell exited with ${child.exitCode ?? child.signalCode} before the renderer reported${
|
||||||
|
failure == null ? '' : `, ${failure.trim()}`
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
observed = {log: observeLog(logPath), page: await servedVersionInPage(debugPort)};
|
observed = {log: observeLog(logPath), page: await servedVersionInPage(debugPort)};
|
||||||
if (
|
if (
|
||||||
observed.page === version &&
|
observed.page === version &&
|
||||||
@@ -282,7 +303,9 @@ async function waitForRenderer({logPath, debugPort, version, timeoutMs, label})
|
|||||||
fail(
|
fail(
|
||||||
`${label}: expected renderer ${version}, the shell serves ${observed.log.served?.version ?? 'nothing'} from ${
|
`${label}: expected renderer ${version}, the shell serves ${observed.log.served?.version ?? 'nothing'} from ${
|
||||||
observed.log.served?.source ?? 'nowhere'
|
observed.log.served?.source ?? 'nowhere'
|
||||||
}, the window loads ${observed.page ?? 'nothing'} and reports ${observed.log.reportedBuild ?? 'nothing'}`,
|
}, the window loads ${observed.page ?? 'nothing'} and reports ${observed.log.reportedBuild ?? 'nothing'}${
|
||||||
|
startupFailure(logPath) == null ? '' : `, ${startupFailure(logPath)}`
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +361,7 @@ async function run() {
|
|||||||
version: firstVersion,
|
version: firstVersion,
|
||||||
timeoutMs: options.timeoutMs,
|
timeoutMs: options.timeoutMs,
|
||||||
label: 'launch',
|
label: 'launch',
|
||||||
|
child: options.update ? null : child,
|
||||||
});
|
});
|
||||||
result.stages.push({stage: 'launch', ...first});
|
result.stages.push({stage: 'launch', ...first});
|
||||||
let final = first;
|
let final = first;
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ enum DesktopStep {
|
|||||||
PackModules,
|
PackModules,
|
||||||
PruneShellRenderer,
|
PruneShellRenderer,
|
||||||
VerifyBundledRendererLinux,
|
VerifyBundledRendererLinux,
|
||||||
|
VerifyBundledRendererWindows,
|
||||||
BuildElectronMain,
|
BuildElectronMain,
|
||||||
InstallVelopackCli,
|
InstallVelopackCli,
|
||||||
BuildAppMacos,
|
BuildAppMacos,
|
||||||
@@ -256,6 +257,7 @@ pub async fn run(args: BuildDesktopArgs) -> Result<()> {
|
|||||||
DesktopStep::PackModules => pack_modules_step(),
|
DesktopStep::PackModules => pack_modules_step(),
|
||||||
DesktopStep::PruneShellRenderer => prune_shell_renderer_step(),
|
DesktopStep::PruneShellRenderer => prune_shell_renderer_step(),
|
||||||
DesktopStep::VerifyBundledRendererLinux => verify_bundled_renderer_linux_step(),
|
DesktopStep::VerifyBundledRendererLinux => verify_bundled_renderer_linux_step(),
|
||||||
|
DesktopStep::VerifyBundledRendererWindows => verify_bundled_renderer_windows_step(),
|
||||||
DesktopStep::BuildElectronMain => build_electron_main_step(),
|
DesktopStep::BuildElectronMain => build_electron_main_step(),
|
||||||
DesktopStep::InstallVelopackCli => install_velopack_cli_step(),
|
DesktopStep::InstallVelopackCli => install_velopack_cli_step(),
|
||||||
DesktopStep::BuildAppMacos => build_app_step(DesktopBuildPlatform::Macos),
|
DesktopStep::BuildAppMacos => build_app_step(DesktopBuildPlatform::Macos),
|
||||||
@@ -1988,6 +1990,36 @@ fn verify_bundled_renderer_linux_step() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_bundled_renderer_windows_step() -> Result<()> {
|
||||||
|
let channel = require_env("BUILD_CHANNEL")?;
|
||||||
|
let version = require_env("BUILD_VERSION")?;
|
||||||
|
let arch = require_env("ARCH")?;
|
||||||
|
let config = windows_package_config(&channel, &arch)?;
|
||||||
|
let unpacked = resolve_windows_unpacked_dir(&arch, &config.main_exe)?;
|
||||||
|
run_command(
|
||||||
|
CommandSpec::new("node")
|
||||||
|
.args(["scripts/release-check.mjs", "--app"])
|
||||||
|
.arg(unpacked.as_os_str())
|
||||||
|
.args([
|
||||||
|
"--channel",
|
||||||
|
channel.as_str(),
|
||||||
|
"--expect-renderer",
|
||||||
|
version.as_str(),
|
||||||
|
"--expect-source",
|
||||||
|
"bundled",
|
||||||
|
"--package-origin",
|
||||||
|
"http://127.0.0.1:9",
|
||||||
|
"--app-arg=--disable-gpu",
|
||||||
|
"--timeout-seconds",
|
||||||
|
"240",
|
||||||
|
]),
|
||||||
|
)?;
|
||||||
|
println!(
|
||||||
|
"The packaged {channel} shell {version} for windows {arch} passes its native module preflight and boots its bundled renderer"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn split_modules_step() -> Result<()> {
|
fn split_modules_step() -> Result<()> {
|
||||||
let renderer_dir = desktop_renderer_dir();
|
let renderer_dir = desktop_renderer_dir();
|
||||||
ensure_renderer_assets_ready(&renderer_dir)?;
|
ensure_renderer_assets_ready(&renderer_dir)?;
|
||||||
|
|||||||
Reference in new issue
Block a user