mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
Simplify CI PR gate
This commit is contained in:
+141
-523
@@ -25,15 +25,15 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
packaged_changes:
|
||||
name: Detect PR change scopes
|
||||
change_scopes:
|
||||
name: Detect CI change scopes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
required: ${{ steps.detect.outputs.required }}
|
||||
daemon_tests_required: ${{ steps.detect.outputs.daemon_tests_required }}
|
||||
web_tests_required: ${{ steps.detect.outputs.web_tests_required }}
|
||||
tools_dev_tests_required: ${{ steps.detect.outputs.tools_dev_tests_required }}
|
||||
tools_pack_tests_required: ${{ steps.detect.outputs.tools_pack_tests_required }}
|
||||
workspace_validation_required: ${{ steps.detect.outputs.workspace_validation_required }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -41,35 +41,19 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect desktop, packaging, and app test scopes
|
||||
- name: Detect workspace and app test scopes
|
||||
id: detect
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
required=false
|
||||
daemon_tests_required=false
|
||||
web_tests_required=false
|
||||
tools_dev_tests_required=false
|
||||
tools_pack_tests_required=false
|
||||
workspace_validation_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" == "apps/daemon/"* || "$file" == "packages/contracts/"* || "$file" == "packages/platform/"* || "$file" == "packages/sidecar/"* || "$file" == "packages/sidecar-proto/"* ]]; then
|
||||
daemon_tests_required=true
|
||||
fi
|
||||
@@ -86,39 +70,54 @@ jobs:
|
||||
if [[ "$file" == "tools/pack/"* || "$file" == "apps/packaged/"* || "$file" == "apps/desktop/"* || "$file" == "packages/platform/"* || "$file" == "packages/sidecar/"* || "$file" == "packages/sidecar-proto/"* ]]; then
|
||||
tools_pack_tests_required=true
|
||||
fi
|
||||
if [[ "$file" == "e2e/specs/mac.spec.ts" || "$file" == "e2e/specs/win.spec.ts" || "$file" == "e2e/specs/linux.spec.ts" || "$file" == "e2e/lib/linux-helpers.ts" || "$file" == "package.json" || "$file" == "pnpm-lock.yaml" || "$file" == "pnpm-workspace.yaml" || "$file" == ".github/workflows/ci.yml" || "$file" == ".github/workflows/release-beta.yml" || "$file" == ".github/workflows/release-preview.yml" || "$file" == ".github/workflows/release-stable.yml" ]]; then
|
||||
required=true
|
||||
if [[ "$file" == "package.json" || "$file" == "pnpm-lock.yaml" || "$file" == "pnpm-workspace.yaml" || "$file" == ".github/workflows/ci.yml" ]]; then
|
||||
daemon_tests_required=true
|
||||
web_tests_required=true
|
||||
tools_dev_tests_required=true
|
||||
tools_pack_tests_required=true
|
||||
fi
|
||||
if [ "$required" = "true" ] \
|
||||
&& [ "$daemon_tests_required" = "true" ] \
|
||||
case "$file" in
|
||||
*.md|*.mdx|*.txt|LICENSE|.gitignore|.editorconfig|.vscode/*|.idea/*|docs/*|.github/ISSUE_TEMPLATE/*|.github/CODEOWNERS)
|
||||
;;
|
||||
apps/landing-page/*|.github/workflows/landing-page-ci.yml|.github/workflows/landing-page-deploy.yml|.github/workflows/blog-indexing-on-deploy.yml|.github/workflows/blog-indexing-monitor.yml)
|
||||
;;
|
||||
*)
|
||||
workspace_validation_required=true
|
||||
;;
|
||||
esac
|
||||
if [ "$daemon_tests_required" = "true" ] \
|
||||
&& [ "$web_tests_required" = "true" ] \
|
||||
&& [ "$tools_dev_tests_required" = "true" ] \
|
||||
&& [ "$tools_pack_tests_required" = "true" ]; then
|
||||
&& [ "$tools_pack_tests_required" = "true" ] \
|
||||
&& [ "$workspace_validation_required" = "true" ]; then
|
||||
break
|
||||
fi
|
||||
done < "$RUNNER_TEMP/changed-files.txt"
|
||||
if [ "$daemon_tests_required" = "true" ] \
|
||||
|| [ "$web_tests_required" = "true" ] \
|
||||
|| [ "$tools_dev_tests_required" = "true" ] \
|
||||
|| [ "$tools_pack_tests_required" = "true" ]; then
|
||||
workspace_validation_required=true
|
||||
fi
|
||||
else
|
||||
required=true
|
||||
daemon_tests_required=true
|
||||
web_tests_required=true
|
||||
tools_dev_tests_required=true
|
||||
tools_pack_tests_required=true
|
||||
workspace_validation_required=true
|
||||
fi
|
||||
{
|
||||
echo "required=$required"
|
||||
echo "daemon_tests_required=$daemon_tests_required"
|
||||
echo "web_tests_required=$web_tests_required"
|
||||
echo "tools_dev_tests_required=$tools_dev_tests_required"
|
||||
echo "tools_pack_tests_required=$tools_pack_tests_required"
|
||||
echo "workspace_validation_required=$workspace_validation_required"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
preflight:
|
||||
name: Preflight
|
||||
needs: [packaged_changes]
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.workspace_validation_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
|
||||
@@ -162,7 +161,7 @@ jobs:
|
||||
pnpm --filter @open-design/web build:sidecar
|
||||
|
||||
- name: Typecheck workspaces
|
||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||
run: pnpm -r --workspace-concurrency=4 --if-present run typecheck
|
||||
|
||||
- name: Check repository layout policies
|
||||
run: pnpm guard
|
||||
@@ -172,7 +171,8 @@ jobs:
|
||||
|
||||
core_tests:
|
||||
name: Core package tests
|
||||
needs: [preflight]
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.workspace_validation_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
@@ -201,12 +201,12 @@ jobs:
|
||||
pnpm --filter @open-design/sidecar test
|
||||
pnpm --filter @open-design/sidecar-proto test
|
||||
|
||||
app_tests:
|
||||
name: App workspace tests
|
||||
needs: [preflight, packaged_changes]
|
||||
if: ${{ needs.packaged_changes.outputs.tools_dev_tests_required == 'true' || needs.packaged_changes.outputs.tools_pack_tests_required == 'true' || needs.packaged_changes.outputs.daemon_tests_required == 'true' || needs.packaged_changes.outputs.web_tests_required == 'true' }}
|
||||
tools_workspace_tests:
|
||||
name: Tools workspace tests
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.tools_dev_tests_required == 'true' || needs.change_scopes.outputs.tools_pack_tests_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -226,35 +226,118 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Prebuild workspace type declarations
|
||||
- name: Tools workspace smoke tests
|
||||
run: |
|
||||
pnpm --filter @open-design/daemon build
|
||||
pnpm --filter @open-design/desktop build
|
||||
pnpm --filter @open-design/web build:sidecar
|
||||
|
||||
- name: App workspace smoke tests
|
||||
if: ${{ needs.packaged_changes.outputs.tools_dev_tests_required == 'true' || needs.packaged_changes.outputs.tools_pack_tests_required == 'true' }}
|
||||
run: |
|
||||
if [ "${{ needs.packaged_changes.outputs.tools_dev_tests_required }}" = "true" ]; then
|
||||
if [ "${{ needs.change_scopes.outputs.tools_dev_tests_required }}" = "true" ]; then
|
||||
pnpm --filter @open-design/tools-dev test
|
||||
fi
|
||||
if [ "${{ needs.packaged_changes.outputs.tools_pack_tests_required }}" = "true" ]; then
|
||||
if [ "${{ needs.change_scopes.outputs.tools_pack_tests_required }}" = "true" ]; then
|
||||
pnpm --filter @open-design/tools-pack test
|
||||
fi
|
||||
|
||||
- name: App workspace daemon tests
|
||||
if: ${{ needs.packaged_changes.outputs.daemon_tests_required == 'true' }}
|
||||
run: |
|
||||
pnpm --filter @open-design/daemon test
|
||||
daemon_workspace_tests:
|
||||
name: Daemon workspace tests
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.daemon_tests_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
- name: App workspace web tests
|
||||
if: ${{ needs.packaged_changes.outputs.web_tests_required == 'true' }}
|
||||
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: Prebuild daemon entrypoint declarations
|
||||
run: pnpm --filter @open-design/daemon build
|
||||
|
||||
- name: Daemon workspace tests
|
||||
run: pnpm --filter @open-design/daemon test
|
||||
|
||||
web_workspace_tests:
|
||||
name: Web workspace tests
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.web_tests_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
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: Prebuild web sidecar declarations
|
||||
run: pnpm --filter @open-design/web build:sidecar
|
||||
|
||||
- name: Web workspace tests
|
||||
run: pnpm --filter @open-design/web test
|
||||
|
||||
app_tests:
|
||||
name: App workspace tests
|
||||
needs:
|
||||
- change_scopes
|
||||
- tools_workspace_tests
|
||||
- daemon_workspace_tests
|
||||
- web_workspace_tests
|
||||
if: ${{ always() && needs.change_scopes.result == 'success' && (needs.change_scopes.outputs.tools_dev_tests_required == 'true' || needs.change_scopes.outputs.tools_pack_tests_required == 'true' || needs.change_scopes.outputs.daemon_tests_required == 'true' || needs.change_scopes.outputs.web_tests_required == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- name: Check app workspace test jobs
|
||||
env:
|
||||
NEEDS_JSON: ${{ toJSON(needs) }}
|
||||
TOOLS_REQUIRED: ${{ needs.change_scopes.outputs.tools_dev_tests_required == 'true' || needs.change_scopes.outputs.tools_pack_tests_required == 'true' }}
|
||||
DAEMON_REQUIRED: ${{ needs.change_scopes.outputs.daemon_tests_required }}
|
||||
WEB_REQUIRED: ${{ needs.change_scopes.outputs.web_tests_required }}
|
||||
run: |
|
||||
pnpm --filter @open-design/web test
|
||||
set -euo pipefail
|
||||
echo "$NEEDS_JSON" | jq .
|
||||
failures=()
|
||||
if [ "$TOOLS_REQUIRED" = "true" ] && [ "$(echo "$NEEDS_JSON" | jq -r '.tools_workspace_tests.result')" != "success" ]; then
|
||||
failures+=("tools_workspace_tests=$(echo "$NEEDS_JSON" | jq -r '.tools_workspace_tests.result')")
|
||||
fi
|
||||
if [ "$DAEMON_REQUIRED" = "true" ] && [ "$(echo "$NEEDS_JSON" | jq -r '.daemon_workspace_tests.result')" != "success" ]; then
|
||||
failures+=("daemon_workspace_tests=$(echo "$NEEDS_JSON" | jq -r '.daemon_workspace_tests.result')")
|
||||
fi
|
||||
if [ "$WEB_REQUIRED" = "true" ] && [ "$(echo "$NEEDS_JSON" | jq -r '.web_workspace_tests.result')" != "success" ]; then
|
||||
failures+=("web_workspace_tests=$(echo "$NEEDS_JSON" | jq -r '.web_workspace_tests.result')")
|
||||
fi
|
||||
if [ "${#failures[@]}" -gt 0 ]; then
|
||||
printf 'App workspace validation failed:\n'
|
||||
printf '%s\n' "${failures[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
e2e_vitest:
|
||||
name: E2E vitest
|
||||
needs: [preflight]
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.workspace_validation_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
@@ -281,7 +364,8 @@ jobs:
|
||||
|
||||
ui_e2e_critical:
|
||||
name: Playwright critical
|
||||
needs: [preflight]
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.workspace_validation_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
@@ -331,62 +415,10 @@ jobs:
|
||||
pnpm -C e2e exec tsx scripts/playwright.ts clean
|
||||
pnpm --filter @open-design/e2e run test:ui:critical
|
||||
|
||||
ui_e2e_extended:
|
||||
name: Playwright extended
|
||||
needs: [preflight]
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
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
|
||||
|
||||
# Cache Playwright browser binaries between runs. The key follows the
|
||||
# @playwright/test version so browser revisions update with package bumps.
|
||||
- name: Resolve Playwright version
|
||||
id: playwright-version
|
||||
run: |
|
||||
version=$(node -p "require('./e2e/package.json').devDependencies['@playwright/test'].replace(/[^0-9.]/g,'')")
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: pnpm -C e2e exec playwright install --with-deps chromium
|
||||
|
||||
- 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: Playwright extended
|
||||
run: |
|
||||
pnpm -C e2e exec tsx scripts/playwright.ts clean
|
||||
pnpm --filter @open-design/e2e run test:ui:extended
|
||||
|
||||
build_workspaces:
|
||||
name: Build workspaces
|
||||
needs: [preflight]
|
||||
needs: [change_scopes]
|
||||
if: ${{ needs.change_scopes.outputs.workspace_validation_required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
@@ -430,7 +462,6 @@ jobs:
|
||||
- app_tests
|
||||
- e2e_vitest
|
||||
- ui_e2e_critical
|
||||
- ui_e2e_extended
|
||||
- build_workspaces
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -449,416 +480,3 @@ jobs:
|
||||
echo "$failures"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
packaged_smoke_mac:
|
||||
name: Packaged mac smoke
|
||||
needs: [preflight, build_workspaces, 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
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Verify mac Electron framework symlinks
|
||||
run: |
|
||||
set -euo pipefail
|
||||
electron_dist="$(node -e 'const path = require("node:path"); const { createRequire } = require("node:module"); const requireFromDesktop = createRequire(path.join(process.cwd(), "apps/desktop/package.json")); const electron = requireFromDesktop.resolve("electron"); process.stdout.write(path.join(path.dirname(electron), "dist"));')"
|
||||
framework="$electron_dist/Electron.app/Contents/Frameworks/Electron Framework.framework"
|
||||
for link in \
|
||||
"$framework/Electron Framework" \
|
||||
"$framework/Helpers" \
|
||||
"$framework/Libraries" \
|
||||
"$framework/Resources" \
|
||||
"$framework/Versions/Current"; do
|
||||
if [ ! -L "$link" ]; then
|
||||
echo "Expected Electron framework symlink, got non-symlink: $link" >&2
|
||||
ls -la "$framework" >&2 || true
|
||||
ls -la "$framework/Versions" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- 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: [preflight, build_workspaces, 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
|
||||
|
||||
- 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 }}
|
||||
|
||||
packaged_smoke_linux_headless:
|
||||
name: Packaged linux headless smoke
|
||||
needs: [preflight, build_workspaces, packaged_changes]
|
||||
if: ${{ needs.packaged_changes.outputs.required == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
# v0.8.0 launch doc explicitly lists Linux as "coming soon" — the
|
||||
# packaged Linux headless runtime is known-incomplete in this release.
|
||||
# Keep the smoke job running so we keep collecting signal, but don't
|
||||
# block PRs on it until the Linux client lands.
|
||||
continue-on-error: true
|
||||
|
||||
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 linux headless artifacts
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tools_pack_dir="$RUNNER_TEMP/tools-pack"
|
||||
report_dir="$RUNNER_TEMP/packaged-report/linux-headless"
|
||||
build_json_path="$report_dir/linux-tools-pack-build.json"
|
||||
build_log_path="$report_dir/linux-tools-pack-build.log"
|
||||
rm -rf "$tools_pack_dir" "$report_dir"
|
||||
mkdir -p "$report_dir"
|
||||
: > "$build_log_path"
|
||||
build_args=(
|
||||
exec tools-pack linux build
|
||||
--dir "$tools_pack_dir"
|
||||
--namespace ci-pr-linux
|
||||
--to dir
|
||||
--json
|
||||
)
|
||||
if build_output="$(pnpm "${build_args[@]}" 2> >(tee -a "$build_log_path" >&2))"; then
|
||||
printf '%s\n' "$build_output" | tee "$build_json_path"
|
||||
node -e 'const fs = require("node:fs"); JSON.parse(fs.readFileSync(process.argv[1], "utf8"));' "$build_json_path"
|
||||
else
|
||||
build_status=$?
|
||||
printf '%s\n' "$build_output" | tee "$build_json_path"
|
||||
exit "$build_status"
|
||||
fi
|
||||
cat > "$report_dir/manifest.json" <<EOF
|
||||
{
|
||||
"platform": "linux",
|
||||
"mode": "headless",
|
||||
"spec": "specs/linux.spec.ts",
|
||||
"namespace": "ci-pr-linux",
|
||||
"githubRunId": "$GITHUB_RUN_ID",
|
||||
"githubRunAttempt": "$GITHUB_RUN_ATTEMPT",
|
||||
"commit": "$GITHUB_SHA"
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Smoke PR linux headless packaged runtime
|
||||
working-directory: e2e
|
||||
env:
|
||||
OD_PACKAGED_E2E_LINUX_HEADLESS: "1"
|
||||
OD_PACKAGED_E2E_NAMESPACE: ci-pr-linux
|
||||
OD_PACKAGED_E2E_TOOLS_PACK_DIR: ${{ runner.temp }}/tools-pack
|
||||
run: |
|
||||
set -euo pipefail
|
||||
report_dir="$RUNNER_TEMP/packaged-report/linux-headless"
|
||||
mkdir -p "$report_dir"
|
||||
pnpm test specs/linux.spec.ts 2>&1 | tee "$report_dir/vitest.log"
|
||||
|
||||
- name: Collect linux headless runtime logs
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
report_dir="$RUNNER_TEMP/packaged-report/linux-headless"
|
||||
runtime_root="$RUNNER_TEMP/tools-pack/runtime/linux/namespaces/ci-pr-linux"
|
||||
if [ -d "$runtime_root" ]; then
|
||||
echo "--- $runtime_root tree ---"
|
||||
find "$runtime_root" -maxdepth 4 -type f | head -40 || true
|
||||
mkdir -p "$report_dir/runtime"
|
||||
# Logs: per-app stdout/stderr captured by tools-pack
|
||||
if [ -d "$runtime_root/logs" ]; then
|
||||
cp -R "$runtime_root/logs" "$report_dir/runtime/logs" || true
|
||||
fi
|
||||
# Runtime state files (identity markers, sidecar JSON)
|
||||
if [ -d "$runtime_root/runtime" ]; then
|
||||
cp -R "$runtime_root/runtime" "$report_dir/runtime/state" || true
|
||||
fi
|
||||
else
|
||||
echo "no runtime root found at $runtime_root"
|
||||
fi
|
||||
|
||||
- name: Upload linux headless e2e spec report
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: open-design-pr-linux-headless-e2e-report
|
||||
path: ${{ runner.temp }}/packaged-report/linux-headless
|
||||
if-no-files-found: warn
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
pnpm --filter @open-design/desktop build
|
||||
|
||||
- name: Typecheck workspaces
|
||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||
run: pnpm -r --workspace-concurrency=4 --if-present run typecheck
|
||||
|
||||
- name: Check repository layout policies
|
||||
run: pnpm guard
|
||||
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
pnpm --filter @open-design/desktop build
|
||||
|
||||
- name: Typecheck workspaces
|
||||
run: pnpm -r --workspace-concurrency=1 --if-present run typecheck
|
||||
run: pnpm -r --workspace-concurrency=4 --if-present run typecheck
|
||||
|
||||
- name: Check repository layout policies
|
||||
run: pnpm guard
|
||||
|
||||
@@ -11,26 +11,17 @@ const releaseBetaWorkflowPath = join(workspaceRoot, ".github", "workflows", "rel
|
||||
const releaseStableWorkflowPath = join(workspaceRoot, ".github", "workflows", "release-stable.yml");
|
||||
|
||||
describe("packaged smoke workflow", () => {
|
||||
it("builds the PR mac smoke artifact without portable mode", async () => {
|
||||
it("keeps packaged smoke outside the main CI gate", async () => {
|
||||
const workflow = await readFile(ciWorkflowPath, "utf8");
|
||||
const macBuildStep = workflow.match(/- name: Build PR mac artifacts\n(?:.+\n)+?(?=\n - name: Smoke PR mac packaged runtime)/m);
|
||||
|
||||
expect(macBuildStep?.[0]).toBeDefined();
|
||||
expect(macBuildStep?.[0]).not.toContain("--portable");
|
||||
});
|
||||
|
||||
it("runs a linux headless packaged smoke job when packaged changes require smoke", async () => {
|
||||
const workflow = await readFile(ciWorkflowPath, "utf8");
|
||||
expect(workflow).toContain("packaged_smoke_linux_headless:");
|
||||
expect(workflow).toContain("e2e/lib/linux-helpers.ts");
|
||||
expect(workflow).toContain("Build PR linux headless artifacts");
|
||||
expect(workflow).toContain('OD_PACKAGED_E2E_LINUX_HEADLESS: "1"');
|
||||
expect(workflow).toContain("pnpm test specs/linux.spec.ts");
|
||||
expect(workflow).toContain("manifest.json");
|
||||
expect(workflow).toContain("linux-tools-pack-build.json");
|
||||
expect(workflow).toContain("Upload linux headless e2e spec report");
|
||||
expect(workflow).toContain("open-design-pr-linux-headless-e2e-report");
|
||||
expectPrLinuxBuildPreservesEvidence(workflow, "Build PR linux headless artifacts");
|
||||
expect(workflow).not.toContain("packaged_smoke_");
|
||||
expect(workflow).not.toContain("Build PR mac artifacts");
|
||||
expect(workflow).not.toContain("Build PR windows artifacts");
|
||||
expect(workflow).not.toContain("Build PR linux headless artifacts");
|
||||
expect(workflow).not.toContain("Smoke PR mac packaged runtime");
|
||||
expect(workflow).not.toContain("Smoke PR windows packaged runtime");
|
||||
expect(workflow).not.toContain("Smoke PR linux headless packaged runtime");
|
||||
expect(workflow).not.toContain("OD_PACKAGED_E2E_");
|
||||
expect(workflow).not.toContain("actions/cache/save");
|
||||
});
|
||||
|
||||
it("preserves beta linux AppImage smoke reports for release publication", async () => {
|
||||
@@ -72,17 +63,6 @@ describe("packaged smoke workflow", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function expectPrLinuxBuildPreservesEvidence(workflow: string, stepName: string): void {
|
||||
const step = workflow.match(new RegExp(`- name: ${stepName}\\n(?:.+\\n)+?(?=\\n - name: Smoke PR linux headless packaged runtime)`, "m"))?.[0];
|
||||
expect(step).toBeDefined();
|
||||
expect(step).toContain('report_dir="$RUNNER_TEMP/packaged-report/linux-headless"');
|
||||
expect(step).toContain('mkdir -p "$report_dir"');
|
||||
expect(step).toContain('build_json_path="$report_dir/linux-tools-pack-build.json"');
|
||||
expect(step).toContain('build_log_path="$report_dir/linux-tools-pack-build.log"');
|
||||
const capturedStdoutWrites = step?.match(/printf '%s\\n' "\$build_output" \| tee "\$build_json_path"/g) ?? [];
|
||||
expect(capturedStdoutWrites).toHaveLength(2);
|
||||
}
|
||||
|
||||
function expectReleaseLinuxBuildPreservesEvidence(workflow: string, stepName: string): void {
|
||||
const step = workflow.match(new RegExp(`- name: ${stepName}\\n(?:.+\\n)+?(?=\\n - name: Smoke .+ linux AppImage runtime)`, "m"))?.[0];
|
||||
expect(step).toBeDefined();
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
"bake:community-pets": "node --experimental-strip-types scripts/bake-community-pets.ts",
|
||||
"seed:test-projects": "node --experimental-strip-types scripts/seed-test-projects.ts",
|
||||
"seed:curated-design-skills": "node --experimental-strip-types scripts/seed-curated-design-skills.ts",
|
||||
"typecheck": "pnpm -r --workspace-concurrency=1 --if-present run typecheck && tsc -p scripts/tsconfig.json --noEmit"
|
||||
"typecheck": "pnpm -r --workspace-concurrency=4 --if-present run typecheck && tsc -p scripts/tsconfig.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@open-design/tools-dev": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user