Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions .github/workflows/build-ts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ jobs:
git config --global core.autocrlf false
- uses: actions/checkout@v5
with:
# Full history so the changed-files lint can diff against the base.
fetch-depth: 0
# pull_request checks out the merge commit. Depth 2 is enough for
# merge-base / HEAD^1. push / merge_group keep a full clone so
# `npm run lint -- --ratchet` can still resolve origin/main.
fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }}
- uses: dorny/paths-filter@v3
id: filter
continue-on-error: true
Expand All @@ -54,36 +56,49 @@ jobs:
ts:
- "ts/**"
- ".github/workflows/build-ts.yml"
# Merge-gate work (install/build/test) still runs on every OS × Node
# cell. Scope only collapses redundant PR ratchets + the base fetch
# onto ubuntu/22. See ts/tools/scripts/prCiScope.mjs.
- name: Decide job scope
id: scope
env:
EVENT_NAME: ${{ github.event_name }}
TS_FILTER: ${{ steps.filter.outputs.ts }}
MATRIX_OS: ${{ matrix.os }}
MATRIX_VERSION: ${{ matrix.version }}
run: node ts/tools/scripts/prCiScope.mjs
- uses: pnpm/action-setup@v4
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.full == 'true' || steps.scope.outputs.lint == 'true' }}
name: Install pnpm
with:
package_json_file: ts/package.json
- uses: actions/setup-node@v5
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.full == 'true' || steps.scope.outputs.lint == 'true' }}
with:
node-version: ${{ matrix.version }}
cache: "pnpm"
cache-dependency-path: ts/pnpm-lock.yaml
- name: Install dependencies
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.full == 'true' || steps.scope.outputs.lint == 'true' }}
working-directory: ts
run: |
pnpm install --frozen-lockfile --strict-peer-dependencies
- name: Build
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.full == 'true' }}
working-directory: ts
run: |
npm run build
- name: Fetch PR base
if: ${{ steps.scope.outputs.fetch == 'true' }}
run: git fetch --no-tags --depth=1 origin "${{ github.base_ref }}"
# On pull requests only changed files are checked (fast); the
# format-pr workflow auto-fixes them. Other events check the whole repo.
- name: Lint
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.lint == 'true' }}
working-directory: ts
shell: bash
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
git fetch --no-tags origin "${{ github.base_ref }}"
node tools/scripts/prettier-changed.mjs --base "origin/${{ github.base_ref }}"
else
npm run lint
Expand All @@ -96,11 +111,10 @@ jobs:
# // code-complexity-allow markers. Tune the thresholds down as hotspots
# get refactored.
- name: Complexity ratchet
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.ratchet == 'true' }}
working-directory: ts
shell: bash
run: |
git fetch --no-tags origin "${{ github.base_ref }}"
npm run code-complexity -- --ratchet --base "origin/${{ github.base_ref }}" \
--cyclomatic 25 --cognitive 30 \
--new-file-cyclomatic 25 --new-file-cognitive 30
Expand All @@ -109,45 +123,42 @@ jobs:
# base branch. Syntactic rules only, so it is fast; the count can only
# trend down. Run `npm run code-lint` locally to see the full report.
- name: Lint ratchet
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.ratchet == 'true' }}
working-directory: ts
shell: bash
run: |
git fetch --no-tags origin "${{ github.base_ref }}"
npm run code-lint -- --ratchet --base "origin/${{ github.base_ref }}"
# Circular-dependency ratchet (PRs only): fail if the change introduces a
# runtime import cycle not already present at the base. Builds the cycle
# set for HEAD and for the merge base (via a throwaway git worktree), so
# this step is heavier than the others (madge runs twice).
- name: Circular dependency ratchet
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.ratchet == 'true' }}
working-directory: ts
shell: bash
run: |
git fetch --no-tags origin "${{ github.base_ref }}"
npm run code-circular -- --ratchet --base "origin/${{ github.base_ref }}" --exceptions-file tools/scripts/code/circular-baseline-exception.json
# Test-debt gate (PRs only): zero tolerance for focused tests
# (.only/fit/fdescribe) and no newly skipped tests (.skip/xit/xdescribe)
# in changed files. A small, fixable problem -> a hard gate, not a ratchet.
- name: Test debt gate
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.ratchet == 'true' }}
working-directory: ts
shell: bash
run: |
git fetch --no-tags origin "${{ github.base_ref }}"
npm run code-debt -- --gate --base "origin/${{ github.base_ref }}"
- name: Restore better-sqlite3 for Node.js
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.full == 'true' }}
working-directory: ts
run: |
pnpm run postinstall:better-sqlite3-node-restore
- name: Test
if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }}
if: ${{ steps.scope.outputs.full == 'true' }}
working-directory: ts
run: |
npm run test:local
- name: UI tests (requires display)
if: ${{ (github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false') && runner.os == 'Linux' }}
if: ${{ steps.scope.outputs.full == 'true' && runner.os == 'Linux' }}
working-directory: ts
run: |
Xvfb :99 -screen 0 1600x1200x24 &
Expand Down
105 changes: 85 additions & 20 deletions pipelines/azure-smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,11 @@ jobs:
pool:
vmImage: ubuntu-latest
steps:
# Full history so merge-base / parent diffs resolve on both PR merge
# commits and merge-queue branches.
# PR merge commits only need HEAD and HEAD^1 for the diff below.
# CI / merge-queue builds fetch origin main in the script and treat a
# missing merge-base as "run the tests", so a shallow clone is safe.
- checkout: self
fetchDepth: 0
fetchDepth: 2
- bash: |
set -uo pipefail
# Default to running the tests; only skip when we can positively
Expand Down Expand Up @@ -169,9 +170,9 @@ jobs:
# required status check keeps passing on PRs that do not touch ts/**.
dependsOn: detect_changes
condition: and(succeeded(), eq(dependencies.detect_changes.outputs['detect.tsChanged'], 'true'))
# Generous cap: the Linux leg can run the shell smoke (60m) + live (60m)
# tests back to back. Requires purchased parallelism for hosted agents.
timeoutInMinutes: 150
# Shell/CLI only. Live tests run in parallel on live_linux so this job
# no longer waits for test:live. Requires purchased parallelism.
timeoutInMinutes: 90
strategy:
# Both legs run independently; one failing does not cancel the other
# (equivalent to the GitHub matrix's fail-fast: false).
Expand All @@ -198,14 +199,22 @@ jobs:
displayName: Install libsecret-1-0
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))

- script: |
pnpm exec playwright install --with-deps
displayName: Install Playwright Browsers
workingDirectory: $(buildDirectory)/packages/shell

- script: |
npm run build
displayName: Build
# Playwright browser download and tsc do not share a write path.
# Overlap them so the longer of the two sets the wait, not the sum.
#
# Scope the work to what shell + CLI smoke actually need:
# * playwright.config.ts only defines a chromium project (Electron
# suites still use the Playwright runner). Chromium alone is enough.
# * fluid-build agent-shell|agent-cli --dep covers the packages the
# smoke/shell steps exercise. Full monorepo build stays on the
# live_linux job and on build_ts.
- bash: |
set -euo pipefail
(cd packages/shell && pnpm exec playwright install --with-deps chromium) &
PW_PID=$!
pnpm exec fluid-build "agent-shell|agent-cli" -t build --dep
wait "$PW_PID"
displayName: Build shell+cli + Playwright chromium (overlapped)
workingDirectory: $(buildDirectory)

# Single federated (WIF) login for the whole job. addSpnToEnvironment
Expand Down Expand Up @@ -254,6 +263,8 @@ jobs:
# WorkloadIdentityCredential against the now-stale token file, and a failed
# assertion exchange is a hard error that stops the chain before it reaches
# this task's fresh AzureCliCredential. Clearing them makes it fall through.
# Full shell:test (jest + all Playwright) on every trigger — PR, main,
# and merge-queue. Linux stays on shell:smoke (unchanged from before).
- task: AzureCLI@2
displayName: Shell Tests - full (Windows)
timeoutInMinutes: 60
Expand Down Expand Up @@ -291,14 +302,69 @@ jobs:
export DISPLAY=:99
npm run shell:smoke

# Own AzureCLI@2 login with the published AZURE_* vars cleared — same
# rationale as "Shell Tests - full" above. Non-blocking (continueOnError)
# so a late auth issue won't fail the run.
# Remove provisioned secrets even if a prior step failed.
- pwsh: |
node -e "try{require('fs').unlinkSync('./.env');}catch(e){}"
node -e "try{require('fs').unlinkSync('./config.local.yaml');}catch(e){}"
displayName: Clean up Keys
workingDirectory: $(buildDirectory)
condition: always()

# Live integration tests on every trigger that has ts changes — including
# PullRequest (same suite as main baseline). Own job so shell/CLI legs do
# not serialize behind live; continueOnError matches baseline (a live
# failure does not fail the required pipeline). Parent still waits for
# this job to finish. Build only packages that define test:live (+deps);
# npm run test:live still walks the whole workspace.
- job: live_linux
displayName: Live tests (Linux)
dependsOn: detect_changes
condition: and(succeeded(), eq(dependencies.detect_changes.outputs['detect.tsChanged'], 'true'))
continueOnError: true
timeoutInMinutes: 90
pool:
vmImage: ubuntu-latest
steps:
- template: include-prepare-repo.yml
parameters:
buildDirectory: $(buildDirectory)
nodeVersion: $(nodeVersion)
registry: $(INSTALL_REGISTRY)

- script: |
sudo apt install libsecret-1-0
displayName: Install libsecret-1-0

# Packages that ship a test:live script (see ts/packages/*/package.json
# and LIVE_TEST_PACKAGE_FILTER in prCiScope.mjs). --dep pulls their
# workspace dependencies; full monorepo build is redundant for this suite.
- bash: |
set -euo pipefail
FILTER=$(node tools/scripts/prCiScope.mjs --live-package-filter)
echo "livePackageFilter=$FILTER"
pnpm exec fluid-build "$FILTER" -t build --dep
displayName: Build live packages (+deps)
workingDirectory: $(buildDirectory)

- task: AzureCLI@2
displayName: Azure login + Get Keys
inputs:
azureSubscription: $(azureSubscription)
scriptType: pscore
scriptLocation: inlineScript
addSpnToEnvironment: true
workingDirectory: $(buildDirectory)
inlineScript: |
$tokenFile = Join-Path "$(Agent.TempDirectory)" "wif-federated-token.txt"
Set-Content -Path $tokenFile -Value "$env:idToken" -NoNewline
Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId"
Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId"
Write-Host "##vso[task.setvariable variable=AZURE_FEDERATED_TOKEN_FILE]$tokenFile"
node tools/scripts/getKeys.mjs --vault build-pipeline-kv --commit

- task: AzureCLI@2
displayName: Live Tests (Linux)
timeoutInMinutes: 60
continueOnError: true
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs:
azureSubscription: $(azureSubscription)
scriptType: bash
Expand All @@ -308,7 +374,6 @@ jobs:
unset AZURE_CLIENT_ID AZURE_TENANT_ID AZURE_FEDERATED_TOKEN_FILE
npm run test:live

# Remove provisioned secrets even if a prior step failed.
- pwsh: |
node -e "try{require('fs').unlinkSync('./.env');}catch(e){}"
node -e "try{require('fs').unlinkSync('./config.local.yaml');}catch(e){}"
Expand Down
1 change: 1 addition & 0 deletions ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"test:keys": "npx tsx tools/scripts/testServiceKeys.ts",
"test:live": "pnpm -r ---no-bail -no-sort --stream --workspace-concurrency=1 run test:live",
"test:local": "pnpm -r --no-bail --no-sort --stream --workspace-concurrency=3 run test:local",
"test:pr-ci-scope": "node --test tools/scripts/test/prCiScope.spec.mjs",
"test:ui": "pnpm -r --no-bail --no-sort --stream --if-present run test:ui"
},
"devDependencies": {
Expand Down
11 changes: 6 additions & 5 deletions ts/tools/scripts/code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,13 @@ Four code-quality steps run in
[`build-ts.yml`](../../../../.github/workflows/build-ts.yml), **on pull requests
only**, sequenced after `Build` and before `Test`. They are skipped entirely
unless the PR touches `ts/**` or the workflow file itself (a `dorny/paths-filter`
guard), and — like the rest of the job — they run on every matrix cell
(`ubuntu`/`windows`/`macos` × Node 22/24).
guard). The gates are repo-wide, not OS-specific, so they run once on the
`ubuntu-latest` + Node 22 cell (see `ts/tools/scripts/prCiScope.mjs`).

Each step is a **changed-files diff against the PR's base branch**: it first
`git fetch --no-tags origin <base_ref>`, then passes `--base origin/<base_ref>`
so only what the PR actually touches is judged. Two flavors:
Each step is a **changed-files diff against the PR's base branch**: the
workflow fetches `origin/<base_ref>` once, then every gate passes
`--base origin/<base_ref>` so only what the PR actually touches is judged. Two
flavors:

- **Ratchet** (`--ratchet`) — _stateless_: the base branch _is_ the baseline
(there is no committed baseline file), so the metric can only trend down.
Expand Down
Loading
Loading