# CI: build -> style (Prettier) -> typecheck (tsc) -> unit tests (vitest) -> live e2e (DeepSeek). # Build first: core's exports point at dist/, and cli's type resolution and runtime imports both need core's build output. # e2e needs the repo secret DEEPSEEK_API_KEY; when absent (e.g. forks) that step self-skips and the other checks run as usual. # A second job repeats build/typecheck/test on windows-latest (see ci-windows below). name: CI # Limit triggers to avoid duplicate runs: push runs only on main/dev; PRs always run once (no target-branch filter -- # this repo's PRs often target integration branches rather than main/dev, and a target filter would leave them with no CI). on: push: branches: [main, dev] pull_request: workflow_dispatch: concurrency: group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 # pnpm version comes from package.json's packageManager field. - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v5 with: node-version: 24 cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build (tsup) run: pnpm build - name: Code style (Prettier) run: pnpm format:check - name: Typecheck (tsc) run: pnpm typecheck - name: Unit tests (vitest) run: pnpm test - name: Installer bundle and POSIX installer tests run: sh scripts/test-installer.sh # The secret is exposed only in this step (step-level env); earlier steps and third-party actions can't see it. # The secrets context can't be used in if expressions, so skip inside the shell when it's absent (e.g. forks). - name: E2E (live LLM via DeepSeek) env: DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} run: | if [ -z "$DEEPSEEK_API_KEY" ]; then echo "DEEPSEEK_API_KEY not available; skipping e2e." exit 0 fi pnpm test:e2e # Windows: the same build/typecheck/test gates on windows-latest (the ubuntu job above stays the # required one). The runner ships Git-Bash on PATH, so core's exec_command tests run through the # shell resolver's bash pick; genuinely POSIX-only tests skip themselves via process.platform # guards (not CI filters), so local Windows devs see the same behavior. Line endings come from # .gitattributes (LF working tree), keeping build outputs and fixtures byte-identical. # No format:check (same files as ubuntu) and no live-LLM e2e here (ubuntu-only budget). ci-windows: runs-on: windows-latest steps: - uses: actions/checkout@v5 # pnpm version comes from package.json's packageManager field. - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v5 with: node-version: 24 cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build (tsup) run: pnpm build - name: Typecheck (tsc) run: pnpm typecheck - name: Unit tests (vitest) run: pnpm test # The Linux job cannot validate PowerShell syntax; parse (never execute) the installer # scripts with the real PowerShell parser so a broken install.ps1 cannot ship. - name: Parse installer scripts (PowerShell) shell: pwsh run: | $failed = $false foreach ($f in @("install.ps1", "packages/landing/public/install.ps1")) { $tokens = $null $errors = $null # .Path: hand ParseFile a plain string, not a PathInfo object. [System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $f).Path, [ref]$tokens, [ref]$errors) | Out-Null if ($errors.Count -gt 0) { $failed = $true Write-Host "${f}: $($errors.Count) parse error(s)" $errors | ForEach-Object { Write-Host " $($_.Extent.StartLineNumber): $($_.Message)" } } else { Write-Host "${f}: OK" } } if ($failed) { exit 1 } - name: Windows installer tests shell: pwsh run: ./scripts/test-installer.ps1