Skip to content

Commit 5d7c9dd

Browse files
ci: add nightly CI workflow that runs all workspaces
Adds a scheduled nightly workflow that runs the full CI suite across all workspaces with Node 22 and 24, plus a helper script to list workspaces with optional filtering via workflow_dispatch input. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
1 parent f2a4ef9 commit 5d7c9dd

2 files changed

Lines changed: 293 additions & 0 deletions

File tree

.github/workflows/ci-nightly.yml

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
name: CI Nightly
2+
3+
on:
4+
# Run every night at 00:00 UTC
5+
schedule:
6+
- cron: '0 0 * * *'
7+
# Allow manual runs, optionally scoped to a subset of workspaces
8+
workflow_dispatch:
9+
inputs:
10+
workspaces:
11+
description: 'Optional JSON string array of workspaces to run, e.g. ["global-header","theme"]. When empty all workspaces run.'
12+
required: false
13+
type: string
14+
15+
jobs:
16+
list-workspaces:
17+
name: List workspaces
18+
runs-on: ubuntu-latest
19+
outputs:
20+
workspaces: ${{ steps.list-workspaces.outputs.workspaces }}
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
24+
25+
- name: Set up Node
26+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
27+
with:
28+
node-version: 24
29+
registry-url: https://registry.npmjs.org/ # Needed for auth
30+
31+
- name: List workspaces
32+
id: list-workspaces
33+
run: node ./scripts/ci/list-all-workspaces.js
34+
env:
35+
WORKSPACES: ${{ inputs.workspaces }}
36+
37+
ci:
38+
name: Workspace ${{ matrix.workspace }}, CI step for node ${{ matrix.node-version }}
39+
runs-on: ubuntu-latest
40+
needs: list-workspaces
41+
strategy:
42+
matrix:
43+
workspace: ${{ fromJSON(needs.list-workspaces.outputs.workspaces) }}
44+
node-version: [22, 24]
45+
fail-fast: false
46+
defaults:
47+
run:
48+
working-directory: ./workspaces/${{ matrix.workspace }}
49+
50+
env:
51+
CI: true
52+
NODE_OPTIONS: --max-old-space-size=8192
53+
54+
steps:
55+
- name: Checkout
56+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
57+
58+
- name: Set up Node ${{ matrix.node-version }}
59+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
60+
with:
61+
node-version: ${{ matrix.node-version }}
62+
registry-url: https://registry.npmjs.org/ # Needed for auth
63+
64+
- name: yarn install
65+
run: yarn install --immutable
66+
67+
- name: check for missing repo fixes
68+
run: yarn fix --check
69+
70+
- name: validate config
71+
if: ${{ hashFiles(format('workspaces/{0}/app-config.yaml', matrix.workspace)) != '' }}
72+
run: yarn backstage-cli config:check --lax
73+
74+
- name: type checking and declarations
75+
run: yarn tsc:full
76+
77+
- name: prettier
78+
run: yarn prettier:check
79+
80+
- name: read knip-reports flag from bcp.json
81+
id: bcp
82+
run: |
83+
echo "Checking for bcp.json in workspace: workspaces/${{ matrix.workspace }}"
84+
if [ -f bcp.json ]; then
85+
echo "Reading knip-reports flag..."
86+
KNIP_REPORTS=$(jq -r '.["knip-reports"]' bcp.json)
87+
echo "knip-reports value: $KNIP_REPORTS"
88+
echo "knip_reports=$KNIP_REPORTS" >> $GITHUB_OUTPUT
89+
else
90+
echo "bcp.json not found. Defaulting knip_reports to false."
91+
echo "knip_reports=false" >> $GITHUB_OUTPUT
92+
fi
93+
94+
- name: check knip reports
95+
if: ${{ steps.bcp.outputs.knip_reports == 'true' }}
96+
run: yarn build:knip-reports --ci
97+
98+
- name: check api reports and generate API reference
99+
run: yarn build:api-reports:only --ci
100+
101+
- name: build all packages
102+
run: yarn backstage-cli repo build --all
103+
104+
- name: lint
105+
run: yarn backstage-cli repo lint --since origin/main
106+
107+
- name: publish check
108+
run: yarn backstage-cli repo fix --check --publish
109+
110+
- name: Install jest-junit reporter
111+
run: |
112+
npm install jest-junit@17.0.0 --ignore-scripts --prefix ${{ runner.temp }}/jest-junit
113+
mkdir -p ${{ runner.temp }}/test-results/${{ matrix.workspace }}
114+
115+
- name: Test changed packages
116+
id: tests
117+
env:
118+
JEST_JUNIT_OUTPUT_DIR: ${{ runner.temp }}/test-results/${{ matrix.workspace }}
119+
JEST_JUNIT_CLASSNAME: '{filepath}'
120+
JEST_JUNIT_UNIQUE_OUTPUT_NAME: 'true'
121+
run: yarn test:all --maxWorkers=3 --reporters=default --reporters=${{ runner.temp }}/jest-junit/node_modules/jest-junit
122+
123+
- name: Upload coverage to Codecov
124+
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
125+
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
126+
with:
127+
flags: ${{ matrix.workspace }}
128+
token: ${{ secrets.CODECOV_TOKEN }}
129+
fail_ci_if_error: false
130+
131+
- name: Upload test results to Codecov
132+
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
133+
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
134+
with:
135+
flags: ${{ matrix.workspace }}
136+
token: ${{ secrets.CODECOV_TOKEN }}
137+
directory: ${{ runner.temp }}/test-results/${{ matrix.workspace }}
138+
fail_ci_if_error: false
139+
140+
- name: install playwright
141+
if: ${{ hashFiles(format('workspaces/{0}/playwright.config.ts', matrix.workspace)) != '' }}
142+
run: yarn playwright install --with-deps chromium chrome
143+
144+
- name: run playwright tests
145+
if: ${{ hashFiles(format('workspaces/{0}/playwright.config.ts', matrix.workspace)) != '' }}
146+
run: yarn playwright test
147+
148+
- name: ensure clean working directory
149+
run: |
150+
if files=$(git ls-files --exclude-standard --others --modified) && [[ -z "$files" ]]; then
151+
exit 0
152+
else
153+
echo ""
154+
echo "Working directory has been modified:"
155+
echo ""
156+
git status --short
157+
echo ""
158+
exit 1
159+
fi
160+
161+
verify:
162+
name: Workspace ${{ matrix.workspace }}, Verify step
163+
runs-on: ubuntu-latest
164+
needs: list-workspaces
165+
strategy:
166+
matrix:
167+
workspace: ${{ fromJSON(needs.list-workspaces.outputs.workspaces) }}
168+
fail-fast: false
169+
steps:
170+
- name: Checkout
171+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
172+
- name: Setup node
173+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
174+
with:
175+
node-version: 24
176+
- name: Install root dependencies
177+
run: yarn install --immutable
178+
- name: Verify lockfile duplicates
179+
run: node scripts/ci/verify-lockfile-duplicates.js workspaces/${{ matrix.workspace }}/yarn.lock
180+
- name: Verify changesets
181+
run: node scripts/ci/verify-changesets.js ${{ matrix.workspace }}
182+
183+
result:
184+
if: ${{ always() }}
185+
name: check all required jobs
186+
runs-on: ubuntu-latest
187+
needs: [ci, verify]
188+
steps:
189+
- run: exit 1
190+
if: >-
191+
${{
192+
contains(needs.*.result, 'failure')
193+
|| contains(needs.*.result, 'cancelled')
194+
|| contains(needs.*.result, 'skipped')
195+
}}

scripts/ci/list-all-workspaces.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import fs from 'fs';
17+
import path from 'path';
18+
import { EOL } from 'os';
19+
20+
/**
21+
* Lists the workspaces that should run in the nightly CI workflow.
22+
*
23+
* When the WORKSPACES environment variable is set it is parsed as a JSON
24+
* string array and used as an explicit allow list. Otherwise every workspace
25+
* that contains a package.json is returned.
26+
*/
27+
function parseRequestedWorkspaces() {
28+
const raw = process.env.WORKSPACES?.trim();
29+
if (!raw) {
30+
return undefined;
31+
}
32+
33+
let parsed;
34+
try {
35+
parsed = JSON.parse(raw);
36+
} catch (error) {
37+
throw new Error(
38+
`Failed to parse WORKSPACES as JSON: ${error.message}. Expected a JSON string array, e.g. ["global-header","theme"].`,
39+
);
40+
}
41+
42+
if (
43+
!Array.isArray(parsed) ||
44+
!parsed.every(item => typeof item === 'string')
45+
) {
46+
throw new Error(
47+
`WORKSPACES must be a JSON array of strings, e.g. ["global-header","theme"], but received: ${raw}`,
48+
);
49+
}
50+
51+
return parsed;
52+
}
53+
54+
function listAllWorkspaces(workspacesDir) {
55+
return fs
56+
.readdirSync(workspacesDir, { withFileTypes: true })
57+
.filter(dirent => dirent.isDirectory())
58+
.map(dirent => dirent.name)
59+
.filter(name =>
60+
fs.existsSync(path.join(workspacesDir, name, 'package.json')),
61+
)
62+
.sort();
63+
}
64+
65+
function main() {
66+
if (!process.env.GITHUB_OUTPUT) {
67+
throw new Error('GITHUB_OUTPUT environment variable not set');
68+
}
69+
70+
const workspacesDir = path.resolve('workspaces');
71+
const allWorkspaces = listAllWorkspaces(workspacesDir);
72+
73+
const requested = parseRequestedWorkspaces();
74+
75+
let workspaces;
76+
if (requested) {
77+
const unknown = requested.filter(name => !allWorkspaces.includes(name));
78+
if (unknown.length > 0) {
79+
throw new Error(
80+
`The following requested workspaces do not exist: ${unknown.join(
81+
', ',
82+
)}. Available workspaces: ${allWorkspaces.join(', ')}`,
83+
);
84+
}
85+
workspaces = requested;
86+
} else {
87+
workspaces = allWorkspaces;
88+
}
89+
90+
console.log('workspaces to run:', workspaces);
91+
92+
fs.appendFileSync(
93+
process.env.GITHUB_OUTPUT,
94+
`workspaces=${JSON.stringify(workspaces)}${EOL}`,
95+
);
96+
}
97+
98+
main();

0 commit comments

Comments
 (0)