Skip to content

Build: Add NX check #678

Build: Add NX check

Build: Add NX check #678

Workflow file for this run

name: nx
on:
push:
branches:
- next
pull_request:
types: [opened, synchronize, labeled, reopened]
permissions:
actions: read
contents: read
statuses: write
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
jobs:
nx:
if: >
(github.event_name == 'pull_request' &&
(contains(github.event.pull_request.labels.*.name, 'ci:normal') ||
contains(github.event.pull_request.labels.*.name, 'ci:merged') ||
contains(github.event.pull_request.labels.*.name, 'ci:daily'))
) || (github.event_name == 'push' && github.ref == 'refs/heads/next')
runs-on: ubuntu-latest
env:
ALL_TASKS: compile,check,knip,test,pretty-docs,lint,sandbox,build,e2e-tests,e2e-tests-dev,test-runner,vitest-integration,check-sandbox,e2e-ui,jest,vitest,playwright-ct,cypress
steps:
- uses: actions/checkout@v4
with:
filter: tree:0
fetch-depth: 0
- run: npx nx@latest start-ci-run --distribute-on="./.nx/workflows/distribution-config.yaml" --stop-agents-after="$ALL_TASKS"
- name: Create Nx Cloud Status (pending)
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const isPr = !!pr;
const sha = isPr ? pr.head.sha : context.sha;
const branchParam = isPr
? pr.number
: (context.ref.startsWith('refs/heads/')
? context.ref.slice('refs/heads/'.length)
: context.ref);
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.payload.pull_request.head.sha,
state: 'pending',
target_url: `https://cloud.nx.app/orgs/606dcb5cdc2a2b00059cc0e9/workspaces/6929fbef73e98d8094d2a343/overview?branch=${prNumber}`,
description: 'NX is running your tests',
context: 'Nx Cloud'
});
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
- run: yarn install --immutable
- uses: nrwl/nx-set-shas@v4
# --- PRs ---
- if: github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'ci:normal')
id: tag
name: 'Set tag to ci:normal'
run: echo "tag=tag:ci:normal" >> $GITHUB_OUTPUT
# - if: github.event_name == 'pull_request' &&
# contains(github.event.pull_request.labels.*.name, 'ci:merged')
# id: tag
# name: 'Set tag to ci:merged'
# run: echo "tag=tag:ci:normal,tag:ci:merged" >> $GITHUB_OUTPUT
# - if: github.event_name == 'pull_request' &&
# contains(github.event.pull_request.labels.*.name, 'ci:daily')
# id: tag
# name: 'Set tag to ci:daily'
# run: echo "tag=tag:ci:normal,tag:ci:merged,tag:ci:daily" >> $GITHUB_OUTPUT
# - if: github.event_name == 'push' && github.ref == 'refs/heads/next'
# id: tag
# name: 'Set tag to ci:merged'
# run: echo "tag=tag:ci:normal,tag:ci:merged" >> $GITHUB_OUTPUT
- id: nx
name: 'Run nx'
run: |
echo 'nx_output<<EOF' >> "$GITHUB_OUTPUT"
yarn nx run-many -t $ALL_TASKS -c production -p="${{ steps.tag.outputs.tag }}" | tee -a "$GITHUB_OUTPUT"
status=${PIPESTATUS[0]}
echo 'EOF' >> "$GITHUB_OUTPUT"
exit $status
- name: Create per-task Nx statuses
if: always()
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const raw = ${{ toJson(steps.nx.outputs.nx_output) }};
if (!raw) {
core.info('No nx_output found, skipping per-task statuses.');
return;
}
const lines = raw.split('\n');
const failures = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line.includes('✖')) continue;
// Example:
// │ ✖ vue3-vite/default-ts:test-runner:production 58s Cache Miss │
const taskMatch = line.match(/✖\s+([^│]+?)\s{2,}/);
const task = taskMatch ? taskMatch[1].trim() : 'Unknown Nx task';
// Look ahead for "> Task logs: https://cloud.nx.app/logs/..."
let url = null;
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
const logLine = lines[j];
const urlMatch = logLine.match(/Task logs:\s*(https:\/\/cloud\.nx\.app\/logs\/\S+)/);
if (urlMatch) {
url = urlMatch[1];
break;
}
}
failures.push({ task, url });
}
if (!failures.length) {
core.info('No failing Nx tasks detected in output.');
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const sha =
context.payload.pull_request?.head?.sha ||
context.sha;
core.info(`Creating statuses for ${failures.length} failing Nx tasks...`);
for (const { task, url } of failures.slice(0, 5)) {
const contextName = `nx run ${task}`;
const description = ` Your tests failed on NX Cloud`;
core.info(`Status for "${task}" -> ${url || 'no URL'}`);
await github.rest.repos.createCommitStatus({
owner,
repo,
sha,
state: 'failure',
target_url: url || undefined,
context: contextName,
description,
});
}
- name: Finalize Nx Cloud Status
if: always()
uses: actions/github-script@v7
with:
script: |
const raw = ${{ toJson(steps.nx.outputs.nx_output) }} || '';
// Try to find a direct run URL in the Nx output
let runUrl = null;
const runMatches = raw.match(/https:\/\/cloud\.nx\.app\/runs\/\S+/g);
if (runMatches && runMatches.length > 0) {
// Use the last run URL in the output
runUrl = runMatches[runMatches.length - 1];
}
const prNumber = context.payload.pull_request.number;
const overviewUrl =
prNumber ? `https://cloud.nx.app/orgs/606dcb5cdc2a2b00059cc0e9/workspaces/6929fbef73e98d8094d2a343/overview?branch=${prNumber}` : '';
const nxCloudUrl = runUrl || overviewUrl;
const jobStatus = '${{ job.status }}'; // 'success', 'failure', 'cancelled', 'skipped'
/** Map job.status -> GitHub Statuses API state */
const stateMap = {
success: 'success',
failure: 'failure',
cancelled: 'error',
skipped: 'error'
};
const state = stateMap[jobStatus] ?? 'error';
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.payload.pull_request.head.sha,
state,
target_url: nxCloudUrl,
description: state === 'success'
? 'Nx Cloud run finished successfully'
: `Nx Cloud run ended with status: ${jobStatus}`,
context: 'Nx Cloud'
});