mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* test: strengthen e2e PR coverage * fix: address e2e PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * ci: cache Windows packaged smoke builds * test: fake additional agent runtimes * fix: address e2e PR feedback Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Route tools-pack mac starts through a launch-time packaged config override so portable packaged smoke runs keep using the namespace runtime root that inspect and logs expect. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Fall back to the packaged app's embedded config when the build output config is missing so installed mac starts still work. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: align packaged mac PR smoke with tools-pack runtime mode Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Keep blake3-wasm out of the packaged mac daemon prebundle so the standalone runtime loads the Cloudflare asset hasher from node_modules instead of crashing in ESM. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address e2e PR feedback Skip the portable mac launch override when the bundled packaged config is missing so installed fallback app targets can still boot with packaged defaults. Add a regression test covering the missing-config start path. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(pack): remove duplicate mac prebundle dependency key
441 lines
17 KiB
YAML
441 lines
17 KiB
YAML
name: ci
|
|
|
|
on:
|
|
pull_request:
|
|
# Release validation is owned by the release workflows rather than this CI
|
|
# workflow: `release-stable` has a verify job before publishing, and
|
|
# `release-beta` builds from its selected release commit. Keep this trigger
|
|
# focused on PRs, main, and manual reruns instead of duplicating tag/release
|
|
# events that would run after those release workflows have already selected
|
|
# or validated their commit.
|
|
push:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ci-${{ github.event.pull_request.number || github.ref }}
|
|
# Prefer current-head signal over preserving superseded logs: PR authors often
|
|
# push fixups while this workflow is still running, and stale runs can report
|
|
# failures for commits reviewers no longer need to evaluate. Release workflows
|
|
# use cancel-in-progress: false where preserving build evidence matters more.
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
packaged_changes:
|
|
name: Detect packaged smoke changes
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
required: ${{ steps.detect.outputs.required }}
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6.0.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Detect desktop/sidecar/packaging changes
|
|
id: detect
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
required=false
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" > "$RUNNER_TEMP/changed-files.txt"
|
|
patterns=(
|
|
"apps/desktop/"
|
|
"apps/packaged/"
|
|
"apps/daemon/src/sidecar/"
|
|
"apps/web/sidecar/"
|
|
"packages/platform/"
|
|
"packages/sidecar/"
|
|
"packages/sidecar-proto/"
|
|
"tools/pack/"
|
|
"e2e/lib/desktop/"
|
|
)
|
|
while IFS= read -r file; do
|
|
for pattern in "${patterns[@]}"; do
|
|
if [[ "$file" == "$pattern"* ]]; then
|
|
required=true
|
|
fi
|
|
done
|
|
if [[ "$file" == "e2e/specs/mac.spec.ts" || "$file" == "e2e/specs/win.spec.ts" || "$file" == "package.json" || "$file" == "pnpm-lock.yaml" || "$file" == "pnpm-workspace.yaml" || "$file" == ".github/workflows/ci.yml" || "$file" == ".github/workflows/release-beta.yml" ]]; then
|
|
required=true
|
|
fi
|
|
if [ "$required" = "true" ]; then
|
|
break
|
|
fi
|
|
done < "$RUNNER_TEMP/changed-files.txt"
|
|
else
|
|
required=true
|
|
fi
|
|
echo "required=$required" >> "$GITHUB_OUTPUT"
|
|
|
|
validate:
|
|
name: Validate workspace
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6.0.2
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v5
|
|
with:
|
|
version: 10.33.2
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Install Playwright browsers
|
|
run: pnpm -C e2e exec playwright install --with-deps chromium
|
|
|
|
# `scripts/postinstall.mjs` only prebuilds package/tool entrypoints that
|
|
# are needed immediately after install for linked bins and shared
|
|
# sidecar/platform imports. It intentionally skips app outputs because
|
|
# building all apps would make every install run a Next/Electron-adjacent
|
|
# app build, even when a developer only needs packages/tools.
|
|
#
|
|
# Fresh CI typecheck/test still need these specific generated declarations:
|
|
# - `apps/daemon/dist/*.d.ts` for packaged/runtime consumers of the daemon
|
|
# package export
|
|
# - `apps/desktop/dist/main/index.d.ts` for `apps/packaged` imports of
|
|
# `@open-design/desktop/main`
|
|
# - `apps/web/dist/sidecar/index.d.ts` for `apps/packaged` imports of
|
|
# `@open-design/web/sidecar`
|
|
# If postinstall grows a targeted app type-generation phase covering these
|
|
# three exports without broad app builds, this CI prebuild can be removed.
|
|
- name: Prebuild workspace type declarations
|
|
run: |
|
|
pnpm --filter @open-design/daemon build
|
|
pnpm --filter @open-design/desktop build
|
|
pnpm --filter @open-design/web build:sidecar
|
|
|
|
- name: Typecheck workspaces
|
|
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
|
|
|
- name: Check repository layout policies
|
|
run: pnpm guard
|
|
|
|
- name: Check i18n structure
|
|
run: pnpm i18n:check
|
|
|
|
- name: Test
|
|
run: |
|
|
pnpm --filter @open-design/e2e test
|
|
pnpm -C e2e exec tsx scripts/playwright.ts clean
|
|
pnpm -C e2e exec playwright test -c playwright.config.ts
|
|
pnpm --filter @open-design/contracts test
|
|
pnpm --filter @open-design/platform test
|
|
pnpm --filter @open-design/sidecar test
|
|
pnpm --filter @open-design/sidecar-proto test
|
|
pnpm --filter @open-design/daemon test
|
|
pnpm --filter @open-design/web test
|
|
pnpm --filter @open-design/tools-dev test
|
|
pnpm --filter @open-design/tools-pack test
|
|
|
|
# Keep workspace builds serialized so generated dist output and local
|
|
# runtime artifacts are produced in a deterministic order. Parallel
|
|
# recursive builds would surface late-package failures sooner, but the
|
|
# current workspace is small enough that safer logs and fewer shared-FS
|
|
# races outweigh the lost parallelism; revisit if the package count grows.
|
|
- name: Build workspaces
|
|
run: pnpm -r --workspace-concurrency=1 --if-present run build
|
|
|
|
packaged_smoke_mac:
|
|
name: Packaged mac smoke
|
|
needs: [validate, packaged_changes]
|
|
if: ${{ needs.packaged_changes.outputs.required == 'true' }}
|
|
runs-on: macos-14
|
|
timeout-minutes: 45
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6.0.2
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v5
|
|
with:
|
|
version: 10.33.2
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build PR mac artifacts
|
|
run: |
|
|
set -euo pipefail
|
|
pnpm exec tools-pack mac build \
|
|
--dir "$RUNNER_TEMP/tools-pack" \
|
|
--namespace ci-pr-mac \
|
|
--mac-compression normal \
|
|
--to all \
|
|
--json
|
|
|
|
- name: Smoke PR mac packaged runtime
|
|
working-directory: e2e
|
|
env:
|
|
OD_PACKAGED_E2E_MAC: "1"
|
|
OD_PACKAGED_E2E_NAMESPACE: ci-pr-mac
|
|
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/tools-pack
|
|
run: pnpm test specs/mac.spec.ts
|
|
|
|
packaged_smoke_win:
|
|
name: Packaged windows smoke
|
|
needs: [validate, packaged_changes]
|
|
if: ${{ needs.packaged_changes.outputs.required == 'true' }}
|
|
runs-on: windows-latest
|
|
timeout-minutes: 60
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6.0.2
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v5
|
|
with:
|
|
version: 10.33.2
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: pnpm
|
|
cache-dependency-path: pnpm-lock.yaml
|
|
|
|
- name: Compute Windows tools-pack cache key
|
|
id: win_tools_pack_cache_key
|
|
shell: pwsh
|
|
run: |
|
|
$epoch = (Get-Date).ToUniversalTime().ToString("yyyy-MM")
|
|
"epoch=$epoch" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
|
|
|
- name: Restore Windows tools-pack cache
|
|
id: win_tools_pack_cache_restore
|
|
uses: actions/cache/restore@v5
|
|
continue-on-error: true
|
|
with:
|
|
path: ${{ runner.temp }}/tools-pack-cache
|
|
key: tools-pack-win-v6-${{ runner.os }}-${{ steps.win_tools_pack_cache_key.outputs.epoch }}-${{ github.sha }}
|
|
restore-keys: |
|
|
tools-pack-win-v6-${{ runner.os }}-${{ steps.win_tools_pack_cache_key.outputs.epoch }}-
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Setup NSIS
|
|
shell: pwsh
|
|
run: |
|
|
if ((Get-Command makensis.exe -ErrorAction SilentlyContinue) -or (Test-Path "C:\Program Files (x86)\NSIS\makensis.exe")) {
|
|
exit 0
|
|
}
|
|
choco install nsis -y --no-progress
|
|
|
|
- name: Build PR windows artifacts
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$toolsPackDir = "${{ runner.temp }}/tools-pack"
|
|
$cacheDir = "${{ runner.temp }}/tools-pack-cache"
|
|
$buildJsonPath = Join-Path $env:RUNNER_TEMP "windows-tools-pack-build.json"
|
|
$buildArgs = @(
|
|
"exec", "tools-pack", "win", "build",
|
|
"--dir", $toolsPackDir,
|
|
"--cache-dir", $cacheDir,
|
|
"--namespace", "ci-pr-win",
|
|
"--portable",
|
|
"--to", "nsis",
|
|
"--json"
|
|
)
|
|
try {
|
|
$buildOutput = pnpm @buildArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows tools-pack cached build exited with code $LASTEXITCODE"
|
|
}
|
|
} catch {
|
|
Write-Warning "Windows tools-pack cached build failed; removing restored cache and retrying with a clean cache. Failure: $_"
|
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $cacheDir
|
|
$buildOutput = pnpm exec tools-pack win build `
|
|
--dir $toolsPackDir `
|
|
--cache-dir $cacheDir `
|
|
--namespace ci-pr-win `
|
|
--portable `
|
|
--to nsis `
|
|
--json
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows tools-pack clean-cache fallback build exited with code $LASTEXITCODE"
|
|
}
|
|
}
|
|
$buildOutput | Set-Content -Path $buildJsonPath
|
|
$buildOutput
|
|
|
|
- name: Smoke PR windows packaged runtime
|
|
working-directory: e2e
|
|
env:
|
|
OD_PACKAGED_E2E_WIN: "1"
|
|
OD_PACKAGED_E2E_NAMESPACE: ci-pr-win
|
|
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/tools-pack
|
|
OD_PACKAGED_E2E_SCREENSHOT_PATH: ${{ runner.temp }}/open-design-win-smoke.png
|
|
run: pnpm test specs/win.spec.ts
|
|
|
|
- name: Prune Windows tools-pack cache
|
|
if: ${{ !cancelled() }}
|
|
shell: pwsh
|
|
continue-on-error: true
|
|
run: |
|
|
$cacheRoot = Join-Path $env:RUNNER_TEMP "tools-pack-cache"
|
|
if (!(Test-Path $cacheRoot)) {
|
|
"tools-pack cache root does not exist; nothing to prune"
|
|
exit 0
|
|
}
|
|
|
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $cacheRoot "locks")
|
|
|
|
$maxBytes = 6GB
|
|
$entryRoot = Join-Path $cacheRoot "entries"
|
|
if (!(Test-Path $entryRoot)) {
|
|
"tools-pack cache entries root does not exist; nothing to prune"
|
|
exit 0
|
|
}
|
|
|
|
$discardedBytes = 0L
|
|
$discardedCount = 0
|
|
$packagedAppRoot = Join-Path $entryRoot "win.packaged-app"
|
|
if (Test-Path $packagedAppRoot) {
|
|
$packagedAppEntries = Get-ChildItem -Path $packagedAppRoot -Directory -ErrorAction SilentlyContinue |
|
|
Where-Object { Test-Path (Join-Path $_.FullName "manifest.json") }
|
|
foreach ($entry in $packagedAppEntries) {
|
|
$size = (Get-ChildItem -Path $entry.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
|
|
Measure-Object -Property Length -Sum).Sum
|
|
Remove-Item -Recurse -Force -LiteralPath $entry.FullName
|
|
$discardedBytes += [int64]($size ?? 0)
|
|
$discardedCount += 1
|
|
}
|
|
Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $packagedAppRoot
|
|
}
|
|
|
|
$priorityByNode = @{
|
|
"win.electron-builder-dir" = 0
|
|
"win.workspace-build" = 1
|
|
"win.resource-tree" = 2
|
|
"win.workspace-tarballs" = 3
|
|
}
|
|
|
|
$entries = Get-ChildItem -Path $entryRoot -Directory -Recurse |
|
|
Where-Object { Test-Path (Join-Path $_.FullName "manifest.json") } |
|
|
ForEach-Object {
|
|
$size = (Get-ChildItem -Path $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
|
|
Measure-Object -Property Length -Sum).Sum
|
|
$node = Split-Path (Split-Path $_.FullName -Parent) -Leaf
|
|
[pscustomobject]@{
|
|
Path = $_.FullName
|
|
Node = $node
|
|
Priority = [int]($priorityByNode[$node] ?? 100)
|
|
Size = [int64]($size ?? 0)
|
|
LastWriteTimeUtc = $_.LastWriteTimeUtc
|
|
}
|
|
} |
|
|
Sort-Object Priority, @{ Expression = "LastWriteTimeUtc"; Descending = $true }
|
|
|
|
$keptBytes = 0L
|
|
$removedBytes = 0L
|
|
$removedCount = 0
|
|
foreach ($entry in $entries) {
|
|
if (($keptBytes + $entry.Size) -le $maxBytes) {
|
|
$keptBytes += $entry.Size
|
|
continue
|
|
}
|
|
Remove-Item -Recurse -Force -LiteralPath $entry.Path
|
|
$removedBytes += $entry.Size
|
|
$removedCount += 1
|
|
}
|
|
|
|
"keptBytes=$keptBytes removedBytes=$removedBytes removedCount=$removedCount discardedBytes=$discardedBytes discardedCount=$discardedCount maxBytes=$maxBytes"
|
|
|
|
- name: Summarize Windows tools-pack build
|
|
if: ${{ !cancelled() }}
|
|
shell: pwsh
|
|
continue-on-error: true
|
|
run: |
|
|
$summaryPath = $env:GITHUB_STEP_SUMMARY
|
|
$buildJsonPath = Join-Path $env:RUNNER_TEMP "windows-tools-pack-build.json"
|
|
if (!(Test-Path $buildJsonPath)) {
|
|
"### Windows tools-pack build" | Add-Content -Path $summaryPath
|
|
"" | Add-Content -Path $summaryPath
|
|
"Build JSON was not found at `$buildJsonPath`." | Add-Content -Path $summaryPath
|
|
exit 0
|
|
}
|
|
|
|
$build = Get-Content -Raw -Path $buildJsonPath | ConvertFrom-Json
|
|
"### Windows tools-pack build" | Add-Content -Path $summaryPath
|
|
"" | Add-Content -Path $summaryPath
|
|
"| Phase | Duration |" | Add-Content -Path $summaryPath
|
|
"| --- | ---: |" | Add-Content -Path $summaryPath
|
|
foreach ($timing in $build.timings) {
|
|
$seconds = [math]::Round(([double]$timing.durationMs) / 1000, 1)
|
|
"| `$($timing.phase)` | ${seconds}s |" | Add-Content -Path $summaryPath
|
|
}
|
|
|
|
"" | Add-Content -Path $summaryPath
|
|
"| Cache node | Status | Reason | Duration |" | Add-Content -Path $summaryPath
|
|
"| --- | --- | --- | ---: |" | Add-Content -Path $summaryPath
|
|
foreach ($entry in $build.cacheReport.entries) {
|
|
$seconds = [math]::Round(([double]$entry.durationMs) / 1000, 1)
|
|
$reason = if ($null -eq $entry.reason) { "" } else { [string]$entry.reason }
|
|
"| `$($entry.nodeId)` | `$($entry.status)` | $reason | ${seconds}s |" | Add-Content -Path $summaryPath
|
|
}
|
|
|
|
$cacheRoot = Join-Path $env:RUNNER_TEMP "tools-pack-cache"
|
|
$entryRoot = Join-Path $cacheRoot "entries"
|
|
if (Test-Path $entryRoot) {
|
|
$entries = Get-ChildItem -Path $entryRoot -Directory -Recurse |
|
|
Where-Object { Test-Path (Join-Path $_.FullName "manifest.json") } |
|
|
ForEach-Object {
|
|
$size = (Get-ChildItem -Path $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
|
|
Measure-Object -Property Length -Sum).Sum
|
|
[pscustomobject]@{
|
|
Node = Split-Path (Split-Path $_.FullName -Parent) -Leaf
|
|
Size = [int64]($size ?? 0)
|
|
}
|
|
} |
|
|
Group-Object Node |
|
|
ForEach-Object {
|
|
[pscustomobject]@{
|
|
Node = $_.Name
|
|
Count = $_.Count
|
|
Size = [int64](($_.Group | Measure-Object -Property Size -Sum).Sum ?? 0)
|
|
}
|
|
} |
|
|
Sort-Object Size -Descending
|
|
|
|
"" | Add-Content -Path $summaryPath
|
|
"| Saved cache node | Entries | Size |" | Add-Content -Path $summaryPath
|
|
"| --- | ---: | ---: |" | Add-Content -Path $summaryPath
|
|
foreach ($entry in $entries) {
|
|
$mb = [math]::Round(([double]$entry.Size) / 1MB, 1)
|
|
"| `$($entry.Node)` | $($entry.Count) | ${mb} MB |" | Add-Content -Path $summaryPath
|
|
}
|
|
}
|
|
|
|
- name: Save Windows tools-pack cache
|
|
if: ${{ success() && steps.win_tools_pack_cache_restore.outputs.cache-hit != 'true' }}
|
|
uses: actions/cache/save@v5
|
|
continue-on-error: true
|
|
with:
|
|
path: ${{ runner.temp }}/tools-pack-cache
|
|
key: tools-pack-win-v6-${{ runner.os }}-${{ steps.win_tools_pack_cache_key.outputs.epoch }}-${{ github.sha }}
|