Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/ai-pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ permissions:
jobs:
generate-title:
uses: ./.github/workflows/reusable-ai-pr-title.yml
secrets: inherit
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
1 change: 1 addition & 0 deletions .github/workflows/ccc-v10-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ permissions:
jobs:
probe:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ permissions:
jobs:
shellcheck:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: Shellcheck all shared scripts
Expand All @@ -27,6 +28,7 @@ jobs:

workflow-lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- name: actionlint (workflows + stubs)
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/commit-types-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ permissions:
jobs:
check:
uses: ./.github/workflows/reusable-commit-types-sync.yml
secrets: inherit
1 change: 0 additions & 1 deletion .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ permissions:
jobs:
commitlint:
uses: ./.github/workflows/reusable-commitlint.yml
secrets: inherit
1 change: 1 addition & 0 deletions .github/workflows/confluence-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ permissions:
jobs:
publish:
runs-on: ubuntu-latest
timeout-minutes: 10
# One publish at a time; a queued push supersedes an unstarted older one.
concurrency:
group: confluence-sync
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/openai-pr-description.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ permissions:
jobs:
openai-pr-description:
uses: ./.github/workflows/reusable-openai-pr-description.yml
secrets: inherit
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ permissions:
jobs:
release:
uses: ./.github/workflows/reusable-release.yml
secrets: inherit
secrets:
RELEASE_DEPLOY_KEY: ${{ secrets.RELEASE_DEPLOY_KEY }}
9 changes: 7 additions & 2 deletions .github/workflows/reusable-ai-pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ on:
default: '24'
secrets:
OPENAI_API_KEY:
description: "Supplied automatically by the stubs' `secrets: inherit`."
description: "Mapped explicitly by the stubs (least privilege — not inherit)."
required: false

jobs:
generate-title:
runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: read
Expand Down Expand Up @@ -80,7 +81,11 @@ jobs:
- name: Install commitlint
if: ${{ steps.commitlint-config.outputs.present == 'true' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
shell: bash
run: npm install --no-save --no-fund --no-audit --loglevel=error @commitlint/cli @commitlint/config-conventional
# Pinned exact: an unpinned install here executes whatever npm serves
# as latest, on every PR, fleet-wide — the same class of risk the
# SHA-pinned actions policy exists for. Keep in step with the
# devDependency majors in package.json when Renovate bumps them.
run: npm install --no-save --no-fund --no-audit --loglevel=error @commitlint/cli@21.2.1 @commitlint/config-conventional@21.2.0

- name: Get Current PR Title
id: get-pr-title
Expand Down
59 changes: 58 additions & 1 deletion .github/workflows/reusable-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ permissions:
jobs:
install:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7

Expand Down Expand Up @@ -89,6 +90,7 @@ jobs:
lint:
if: ${{ vars.CI_SKIP_LINT != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7

Expand All @@ -109,6 +111,58 @@ jobs:
- name: Lint
run: npm run lint --if-present

# Format gate. The fleet's Prettier options are centralised in
# @nswds/prettier-config, but every repo's `format` script is write-mode —
# without a CI check the standard is convention, and drift only surfaces as
# noisy style diffs in later PRs (awards#74 needed a follow-up style commit
# for exactly this). Runs only when the repo has a Prettier config: a
# config file, or the package.json "prettier" key — the latter is how
# nswds-tokens and the config packages consume it, and workflow expressions
# can't read package.json, hence the probe step (same pattern as the
# npm-test probe below). Repos with latent unformatted files opt out with
# CI_SKIP_FORMAT=true while they sweep.
# Not required by rulesets unless a repo adds the "install / format" context.
format:
if: ${{ vars.CI_SKIP_FORMAT != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7

- name: Detect Prettier config
id: prettier
run: |
present=false
if ls .prettierrc .prettierrc.* prettier.config.* >/dev/null 2>&1; then
present=true
elif [ -f package.json ] && node -e "process.exit(require('./package.json').prettier ? 0 : 1)"; then
present=true
fi
Comment on lines +136 to +140
echo "present=$present" >> "$GITHUB_OUTPUT"

- name: Setup Node.js (.nvmrc)
if: ${{ steps.prettier.outputs.present == 'true' && hashFiles('.nvmrc') != '' }}
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
- name: Setup Node.js (fallback)
if: ${{ steps.prettier.outputs.present == 'true' && hashFiles('.nvmrc') == '' }}
uses: actions/setup-node@v7
with:
node-version: ${{ inputs.node-version }}

- name: Install dependencies
if: ${{ steps.prettier.outputs.present == 'true' }}
run: npm clean-install

- name: Check formatting
if: ${{ steps.prettier.outputs.present == 'true' }}
run: npx --no-install prettier --check .

- name: Nothing to run
if: ${{ steps.prettier.outputs.present != 'true' }}
run: echo "No Prettier config file or package.json prettier key — nothing to check."

# Test gate: runs the repo's vitest suite when a vitest config exists,
# otherwise its `npm test` script. Same silent-gap rationale as lint —
# nswds-app's storybook browser suite never ran on PRs. Browser-mode suites
Expand All @@ -127,6 +181,7 @@ jobs:
test:
if: ${{ vars.CI_SKIP_TESTS != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7

Expand Down Expand Up @@ -156,7 +211,9 @@ jobs:

- name: Run vitest
if: ${{ hashFiles('vitest.config.*') != '' }}
run: npx vitest run
# --no-install: a repo with a vitest config but no vitest dependency
# should fail loudly, not have npx fetch and run latest from the registry.
run: npx --no-install vitest run

# No vitest config: fall back to the repo's own `npm test`, if it has a
# real one. Detected in a step rather than an `if:` expression because
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/reusable-commit-types-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ permissions:
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v7
Expand All @@ -34,8 +35,9 @@ jobs:

# Install commitlint without touching the repo's manifest so the check
# runs the same way in this repo (no package.json) and in consumer repos.
# Pinned exact — see the matching note in reusable-ai-pr-title.yml.
- name: Install commitlint
run: npm install --no-save @commitlint/cli @commitlint/config-conventional
run: npm install --no-save @commitlint/cli@21.2.1 @commitlint/config-conventional@21.2.0

- name: Check commit type lists are in sync
run: ./scripts/check-commit-types-sync.sh
1 change: 1 addition & 0 deletions .github/workflows/reusable-commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ permissions:
jobs:
commitlint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# Reusable workflows run in the caller's context: this checks out the
# consumer repo, and its own devDependencies provide commitlint.
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/reusable-openai-pr-description.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_call:
secrets:
OPENAI_API_KEY:
description: "Supplied automatically by the stubs' `secrets: inherit`."
description: "Mapped explicitly by the stubs (least privilege — not inherit)."
required: false

permissions:
Expand All @@ -16,6 +16,7 @@ permissions:
jobs:
openai-pr-description:
runs-on: ubuntu-24.04
timeout-minutes: 10
# Skip for fork PRs — secrets are not available there
if: github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name

Expand Down
55 changes: 52 additions & 3 deletions .github/workflows/reusable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ on:
RELEASE_DEPLOY_KEY:
description: >-
Optional SSH deploy key (ruleset bypass actor) used to push release
commits to a protected default branch. Supplied automatically by the
stubs' `secrets: inherit` where the repo defines it.
commits to a protected default branch. Mapped explicitly by the
stubs (least privilege — not inherit); empty in repos without it.
required: false

jobs:
release:
name: Release
runs-on: ubuntu-latest
timeout-minutes: 30
# One release at a time, never cancelled mid-publish (a cancelled
# semantic-release can tag without publishing).
concurrency:
Expand Down Expand Up @@ -79,4 +80,52 @@ jobs:
# husky hooks must not run against the bot commit (this broke
# nswds-tokens releases when it regressed — see tokens #105/#107).
HUSKY: 0
run: npx semantic-release
# --no-install: every fleet repo carries semantic-release as a
# devDependency; a repo missing it should fail here, not have npx
# fetch latest and run it with contents:write and the deploy key.
run: npx --no-install semantic-release

# Release failures are easy to miss: they happen post-merge where nobody is
# watching, and semantic-release may have already pushed the version tag so
# the repo LOOKS released. File (or bump) a labelled issue so every failure
# generates a notification and a visible artefact in the repo. Ported from
# nswds-ui's bespoke release.yml, where three releases (v1.8.0–v2.1.0) once
# failed without anyone noticing.
alert-on-failure:
name: File release-failure issue
needs: release
if: failure()
Comment on lines +95 to +97
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: write
steps:
- name: Create or update the release-failure issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh label create release-failure \
--repo "$GITHUB_REPOSITORY" \
--color B60205 \
--description "Automated: a Release workflow run failed" \
--force
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
body="A Release workflow run failed — a git tag may exist without its release artefacts.

- Run: ${run_url}
- Commit: ${GITHUB_SHA}
- Repos that publish to npm: check for a git tag without a matching npm version.

_Filed automatically by the alert-on-failure job in reusable-release.yml._"
existing="$(gh issue list --repo "$GITHUB_REPOSITORY" --label release-failure --state open --json number --jq '.[0].number // empty')"
if [ -n "$existing" ]; then
gh issue comment "$existing" --repo "$GITHUB_REPOSITORY" --body "$body"
echo "Commented on existing issue #$existing"
else
gh issue create \
--repo "$GITHUB_REPOSITORY" \
--title "Release workflow failed" \
--label release-failure \
--assignee "$GITHUB_ACTOR" \
--body "$body"
fi
1 change: 1 addition & 0 deletions .github/workflows/reusable-validate-branch-name.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ permissions:
jobs:
check-branch-name:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout base branch policy
uses: actions/checkout@v7
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ permissions:
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 15
# One sync at a time; a queued push supersedes an unstarted older one.
concurrency:
group: repo-sync
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/validate-branch-name.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ permissions:
jobs:
check-branch-name:
uses: ./.github/workflows/reusable-validate-branch-name.yml
secrets: inherit
5 changes: 4 additions & 1 deletion workflow-stubs/ai-pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ permissions:
jobs:
generate-title:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-ai-pr-title.yml@v1
secrets: inherit
secrets:
# Explicit mapping, not inherit — this workflow needs exactly one secret,
# and inherit would hand every repo secret to the called workflow.
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
1 change: 0 additions & 1 deletion workflow-stubs/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ permissions:
jobs:
install:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-ci.yml@v1
secrets: inherit
1 change: 0 additions & 1 deletion workflow-stubs/commit-types-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,3 @@ permissions:
jobs:
check:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-commit-types-sync.yml@v1
secrets: inherit
1 change: 0 additions & 1 deletion workflow-stubs/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ permissions:
jobs:
commitlint:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-commitlint.yml@v1
secrets: inherit
5 changes: 4 additions & 1 deletion workflow-stubs/openai-pr-description.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ permissions:
jobs:
openai-pr-description:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-openai-pr-description.yml@v1
secrets: inherit
secrets:
# Explicit mapping, not inherit — this workflow needs exactly one secret,
# and inherit would hand every repo secret to the called workflow.
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
5 changes: 4 additions & 1 deletion workflow-stubs/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ permissions:
jobs:
release:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-release.yml@v1
secrets: inherit
secrets:
# Explicit mapping, not inherit — the release needs exactly one secret,
# and inherit would hand every repo secret to the called workflow.
RELEASE_DEPLOY_KEY: ${{ secrets.RELEASE_DEPLOY_KEY }}
1 change: 0 additions & 1 deletion workflow-stubs/validate-branch-name.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ permissions:
jobs:
check-branch-name:
uses: digitalnsw/nswds-devops/.github/workflows/reusable-validate-branch-name.yml@v1
secrets: inherit