Skip to content
Open
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
219 changes: 219 additions & 0 deletions .github/workflows/build_website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
name: Build FlowFuse website

# The single definition of a website build. Called by FlowFuse/website's own CI and by
# FlowFuse/flowfuse documentation pull requests, so a change to the build, the link
# check or the preview upload is picked up by both callers without being copied.
on:
workflow_call:
inputs:
website_repository:
description: 'Repository holding the website source.'
type: string
default: 'FlowFuse/website'
website_ref:
description: 'Ref of the website source to build.'
type: string
default: 'main'
docs_repository:
description: 'Repository providing the docs/ tree. Empty lets docs-sync clone FlowFuse/flowfuse main itself.'
type: string
default: ''
docs_ref:
description: 'Ref or commit to check out from docs_repository.'
type: string
default: ''
run_unit_tests:
description: 'Run the website unit tests.'
type: boolean
default: false
deploy_preview:
description: 'Build with images and upload the result to Netlify as an aliased, unpublished deploy.'
type: boolean
default: false
preview_alias:
description: 'Netlify deploy alias. Required when deploy_preview is true.'
type: string
default: ''
pr_number:
description: 'Pull request to comment on with the preview URL. 0 disables the comment.'
type: number
default: 0
secrets:
ci_app_id:
description: 'GitHub App id able to read the private blueprint-library repository.'
required: true
ci_app_key:
description: 'Private key for ci_app_id.'
required: true
netlify_auth_token:
description: 'Required when deploy_preview is true.'
required: false
netlify_site_id:
description: 'Required when deploy_preview is true.'
required: false
outputs:
preview_url:
description: 'URL of the uploaded preview. Empty when deploy_preview is false.'
value: ${{ jobs.build.outputs.preview_url }}

jobs:
build:
name: Build and check
runs-on: ubuntu-latest
outputs:
preview_url: ${{ steps.deploy.outputs.preview_url }}
steps:
- name: Generate a token
id: generate_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.ci_app_id }}
private-key: ${{ secrets.ci_app_key }}
owner: ${{ github.repository_owner }}
# Only the blueprint-library checkout below consumes this token. The website and
# documentation checkouts are public and use the caller's default GITHUB_TOKEN.
repositories: blueprint-library
- name: Check out the website repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.website_repository }}
ref: ${{ inputs.website_ref }}
path: 'website'
# nuxt/lib/docs-sync.mjs prefers a checkout sitting next to the website over cloning
# main, so this is what makes the build render the caller's docs rather than main's.
- name: Check out the documentation source
if: inputs.docs_repository != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ inputs.docs_repository }}
ref: ${{ inputs.docs_ref }}
path: 'flowfuse'
- name: Check out FlowFuse/blueprint-library repository (to access the blueprints)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: 'FlowFuse/blueprint-library'
ref: main
path: 'blueprint-library'
token: ${{ steps.generate_token.outputs.token }}
# Only a preview processes images. Every other build sets SKIP_IMAGES, which drops
# nuxt.config.ts's image provider to none, so there is nothing to cache or restore.
- name: Cache image pipeline output
if: inputs.deploy_preview
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
# Keyed on the source images, so a run that changes no image hits exactly and
# writes nothing back. These are the two directories netlify.toml's cache plugin
# keeps warm on Netlify's own builds.
key: img-pipeline-${{ hashFiles('website/src/**/*.png', 'website/src/**/*.jpg', 'website/src/**/*.jpeg', 'website/src/**/*.gif', 'website/src/**/*.webp', 'website/src/**/*.svg') }}
restore-keys: img-pipeline-
path: |
website/nuxt/public/img
website/.cache/images
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: 'npm'
cache-dependency-path: './website/package-lock.json'
- run: npm run blueprints
working-directory: 'website'
- name: Install Dependencies
run: npm install
working-directory: 'website'
- name: Run unit tests
if: inputs.run_unit_tests
run: npm test
working-directory: 'website'
# A preview needs real images, and nuxt.config.ts drops the image provider to none
# when SKIP_IMAGES is set. This matches netlify.toml's deploy-preview context.
- name: Build the forge
run: npm run ${{ inputs.deploy_preview && 'build:nuxt' || 'build:nuxt:skip-images' }}
working-directory: 'website'
- name: Check links
uses: untitaker/hyperlink@9375bc4063712ad490d5eb3d54df0b6aade15e54 # 0.3.2
with:
args: website/nuxt/dist/ --check-anchors --sources website/src
- name: Upload an unpublished preview to Netlify
id: deploy
if: inputs.deploy_preview
working-directory: 'website'
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.netlify_auth_token }}
NETLIFY_SITE_ID: ${{ secrets.netlify_site_id }}
NETLIFY_TELEMETRY_DISABLED: '1'
PREVIEW_ALIAS: ${{ inputs.preview_alias }}
run: |
if [ -z "${NETLIFY_AUTH_TOKEN}" ] || [ -z "${NETLIFY_SITE_ID}" ]; then
echo "deploy_preview is set but netlify_auth_token or netlify_site_id was not passed." >&2
exit 1
fi
# Deliberately no --prod and no --build. netlify.toml supplies the publish and
# functions directories, and skipping the build means its build command, which
# reindexes Algolia, never runs against a preview.
npx --yes netlify-cli@27.1.0 deploy \
--alias "${PREVIEW_ALIAS}" \
--message "Preview ${PREVIEW_ALIAS}. Do not publish: built outside the production context." \
--json > deploy.json
preview_url="$(jq -r '.deploy_url // empty' deploy.json)"
production_url="$(jq -r '.url // empty' deploy.json)"
if [ -z "${preview_url}" ] || [ "${preview_url}" = "${production_url}" ]; then
echo "Netlify reported the production URL; refusing to publish it as a preview." >&2
exit 1
fi
echo "preview_url=${preview_url}" >> "$GITHUB_OUTPUT"
- name: Record the preview as a deployment
if: inputs.deploy_preview
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
PREVIEW_URL: ${{ steps.deploy.outputs.preview_url }}
with:
script: |
const { owner, repo } = context.repo
const deployment = await github.rest.repos.createDeployment({
owner,
repo,
ref: context.payload.pull_request?.head.sha || context.sha,
environment: 'Preview',
description: 'Website preview of this pull request',
auto_merge: false,
required_contexts: [],
transient_environment: true,
production_environment: false
})
await github.rest.repos.createDeploymentStatus({
owner,
repo,
deployment_id: deployment.data.id,
state: 'success',
environment_url: process.env.PREVIEW_URL,
description: 'Uploaded to Netlify',
auto_inactive: true
})
- name: Comment the preview URL on the pull request
if: inputs.deploy_preview && inputs.pr_number > 0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
PREVIEW_URL: ${{ steps.deploy.outputs.preview_url }}
PR_NUMBER: ${{ inputs.pr_number }}
WEBSITE_SOURCE: ${{ inputs.website_repository }}@${{ inputs.website_ref }}
with:
script: |
// One comment per pull request, rewritten in place, so a busy branch does not
// accumulate a comment per push.
const marker = '<!-- website-preview -->'
const sha = context.payload.pull_request?.head.sha || context.sha
const body = [
marker,
`Website preview: ${process.env.PREVIEW_URL}`,
'',
`Built from ${sha} against ${process.env.WEBSITE_SOURCE}.`,
'This is a preview build, so scheduled blog posts are visible and the deploy is never published.'
].join('\n')
const { owner, repo } = context.repo
const issue_number = Number(process.env.PR_NUMBER)
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
const previous = comments.find(comment => comment.body?.includes(marker))
if (previous) {
await github.rest.issues.updateComment({ owner, repo, comment_id: previous.id, body })
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body })
}
Loading