mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
Static Checks / lint (x64-4 lane): every cache hit since the job moved to this lane ran out of space. The 2.47 GB archive plus the tree it unpacks to does not fit the 16Gi RAM-backed workspace, so each hit fell back to a 1.5-4 h cold install (16 of 16 runs cold since #10303). The Restore step now moves the archive onto the disk-backed package-cache volume (the parent of YARN_CACHE_FOLDER) before extracting, so only the tree lives in RAM. Where YARN_CACHE_FOLDER is unset, or the move fails, it extracts in place exactly as before. Two report-only df lines (after the restore and at the top of Summarize) record workspace headroom and can never fail a step. Triggers: apply the owner decision of 2026-09-21 (already on draft #10254, same text) directly to develop. A pull request INTO develop no longer starts static-checks, typos (cspell), snyk-analysis (trigger removed, restore lines kept in a comment), or build, secrets-analysis and the external-uptime-monitor self-test (branches-ignore: develop, so PRs into stage/master still run them). Every push trigger is unchanged, so all of them still run when code lands on develop. develop has no required status checks, so no PR is blocked by the missing runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
537 lines
30 KiB
YAML
537 lines
30 KiB
YAML
name: Build
|
|
|
|
# Replaces the CircleCI `build` workflow, which could not complete on that plan: a cold
|
|
# `yarn install` for this monorepo takes ~46 minutes and saving the node_modules cache another ~9,
|
|
# which exceeds the 60-minute job cap — so the cache was never written and every run started cold.
|
|
# Self-hosted runners have no such cap.
|
|
#
|
|
# LATENCY IS A CORRECTNESS PROPERTY OF THIS GATE. The 2026-08-13 template-error incident was not
|
|
# a coverage gap — build-web caught the error — but its verdict landed 74 minutes AFTER the PR
|
|
# had been merged on a still-pending check (~3h19m push-to-verdict). Two structural causes fixed
|
|
# here:
|
|
# 1. `build-libs` is its own job: `build:package:all` is where every LIBRARY's strict template
|
|
# check runs (each lib's tsconfig sets strictTemplates — the app tsconfig is lax in every
|
|
# configuration), so the verdict that catches template errors now reports as its own named
|
|
# check ~install+~20min after push instead of at the end of the longest job. Treat a
|
|
# pending/cancelled `build-libs`/`build-web` as a red: NEVER merge on yellow — the PR
|
|
# concurrency group cancels superseded runs, so "no failure" often just means "never ran".
|
|
# 2. The `needs: build-monorepo-root` edges are BACK, because that job now produces something the
|
|
# others consume. They were removed when it produced nothing and the edge was pure serial
|
|
# latency; five jobs then each ran their own `yarn install` of the same tree CONCURRENTLY and
|
|
# fought each other for I/O. Measured on run 32358690732, same commit: build-api installed in
|
|
# 1h49m and passed, build-libs and build-web took 2h40m and were KILLED at the 180-minute
|
|
# ceiling with their build steps never started. build-monorepo-root now installs once and
|
|
# publishes the tree; the other four restore it in minutes. On the warm path (~most PRs) they
|
|
# report EARLIER than before, because the install in front of them is a cache lookup.
|
|
# They carry `if: !cancelled()` so a dead producer still yields a real red — a `needs:` edge
|
|
# alone would SKIP them, and a skipped required check does not block a merge, which is the same
|
|
# "never ran" hazard as (1).
|
|
# The dev-config "Build packages" pre-step was removed from build-api/build-web: the app builds
|
|
# rebuild their dependency libraries themselves (Nx `dependsOn: ^build`, production config), and
|
|
# the strict library verdict lives in `build-libs`.
|
|
|
|
on:
|
|
pull_request:
|
|
# Off by owner decision (2026-09-21): a pull request INTO develop does not start this workflow. The cost
|
|
# belonged to every commit on the branch being merged, and the run belongs to the merge - the push
|
|
# trigger below runs it when code lands on develop. PRs aimed at other branches still run it.
|
|
branches-ignore:
|
|
- develop
|
|
push:
|
|
branches:
|
|
- develop
|
|
- stage
|
|
- master
|
|
|
|
# Unique to this workflow and ref. Deliberately NOT a group shared with other workflows: a shared
|
|
# group serializes unrelated builds and silently cancels them (see the image-build starvation
|
|
# incident). Within this workflow+ref, only the newest commit is built.
|
|
concurrency:
|
|
# Keyed on the HEAD REF, not on `github.ref`, so the two events that fire for one commit land in
|
|
# the same group. `pull_request:` above is unfiltered, so while a release-cascade PR is open whose
|
|
# head is a build branch (today: #10257, `stage` -> `stage-apps`), a single push to `stage` starts
|
|
# BOTH a push run on `refs/heads/stage` AND a pull_request run on `refs/pull/10257/merge`. Under
|
|
# `github.ref` those are different strings, so neither cancelled the other and the fleet compiled
|
|
# the same tree twice — two 3-hour builds per push, for as long as the cascade PR stays open.
|
|
#
|
|
# The trade-off, stated plainly: the pull_request run builds the MERGE commit and the push run
|
|
# builds the branch head. For a fast-forward cascade those are the same tree and the second build
|
|
# is pure waste; if the base has diverged they differ, and collapsing them means only the newer
|
|
# event's view is compiled. That is acceptable here because no branch declares a required status
|
|
# check, so nothing is gated on the verdict that loses — but it IS a real narrowing, not a free win.
|
|
#
|
|
# Qualified by the head REPOSITORY as well as the ref. Fork pull requests are skipped at the job
|
|
# level, but a skipped job still creates a run that claims the group first — so without the repo
|
|
# in the key, a fork branch named `develop` could cancel a genuine `develop` build and then skip.
|
|
group: >-
|
|
build-${{ github.workflow }}-${{ github.event_name == 'pull_request'
|
|
&& format('{0}@{1}', github.event.pull_request.head.repo.full_name, github.event.pull_request.head.ref)
|
|
|| format('{0}@{1}', github.repository, github.ref_name) }}
|
|
# Was `github.event_name == 'pull_request'`, which left branch pushes uncancelled:
|
|
# four merges to develop in an hour meant four full 3-h compiles of commits that were
|
|
# already superseded. This job only compiles — nothing outside the run depends on a
|
|
# half-finished one — so the newest commit always wins.
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# Mirrors the CircleCI `defaults` block. The `ng:*` scripts already set the heap themselves via
|
|
# cross-env, so this covers everything outside them.
|
|
NODE_OPTIONS: --max-old-space-size=12288
|
|
NG_CLI_ANALYTICS: false
|
|
# Nx Cloud is disabled for this org; without this `nx run-many` hard-fails with
|
|
# "Nx Cloud: Workspace is unable to be authorized. Exiting run."
|
|
NX_NO_CLOUD: true
|
|
# The dependency tree travels between jobs as this one compressed file. Workspace-relative so the
|
|
# same string works for `tar` in a run step and for actions/cache.
|
|
NODE_MODULES_ARCHIVE: node-modules.tar.zst
|
|
|
|
# Least-privilege scope for the automatic GITHUB_TOKEN.
|
|
# This workflow only builds/tests/deploys from a checkout — read access is sufficient.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build-monorepo-root:
|
|
name: build-monorepo-root
|
|
# Moved off the 4-core pool: this is no longer one of five equals, it is the SERIAL CRITICAL PATH
|
|
# for the whole gate, and both halves of its work (yarn install, zstd -T0) scale with cores.
|
|
# Never run jobs for a pull request from a fork (owner decision 2026-09-16); branch PRs and pushes still run.
|
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_8 || 'ubuntu-latest' }}
|
|
# 360, not 180. This job now carries the install for the ENTIRE gate, alone, and a cold install
|
|
# here has no yarn tarball cache behind it: test_playwright.yml's equivalent deps job measured the
|
|
# same work at 88 min, 3h20m and 3h46m. At 180 a cold miss would time out, and because the four
|
|
# build jobs `needs:` this one, that single timeout would take the whole gate with it. The ceiling
|
|
# costs nothing on the warm path, which is a lookup and an exit.
|
|
timeout-minutes: 360
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Test native rebuild setup
|
|
run: yarn test:postinstall
|
|
|
|
- name: Test release update-channel guard
|
|
run: yarn test:publish-channel
|
|
|
|
- name: Restore node_modules archive
|
|
id: cache
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
# NOT lookup-only. It was, and that made every warm run fail: on a cache HIT the archive
|
|
# is never written to disk, so `Archive node_modules` is skipped along with the other
|
|
# producer steps, and the UNGUARDED `Upload node_modules archive` below then trips its
|
|
# `if-no-files-found: error`. The cold run that first writes the key passes; the NEXT run,
|
|
# the one the cache exists for, is the one that breaks - the worst place to put a failure.
|
|
# Downloading ~2.9 GB here costs a few minutes against the ~90-minute install it replaces,
|
|
# and it is what guarantees the run-scoped artifact handoff below actually has a file.
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-node-modules-${{ hashFiles('yarn.lock', 'package.json', 'patches/**', '.scripts/postinstall.js') }}
|
|
|
|
# Route the install through the internal Verdaccio cache. Placed AFTER the cache restore on
|
|
# purpose: this action rewrites yarn.lock's resolved URLs, and hashFiles() in a cache key
|
|
# evaluates at step runtime - configuring the registry before the restore would move the key
|
|
# and bifurcate the cache namespace by VIP reachability (the reason #10025 skipped this file).
|
|
# Measured cost of NOT having it here: the bootstrap fallback pulled from the public registry
|
|
# and blew through the 180-minute job ceiling twice on stage run 32513912629.
|
|
- name: Configure Registry
|
|
if: steps.cache.outputs.cache-hit != 'true'
|
|
uses: ever-co/ever-gauzy/.github/actions/configure-registry@aa4ee19926fabcf820aa1294385a76aec6bdb548
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# Not contains(matrix.os, ...): these jobs define no matrix.os, so that expression is
|
|
# always false and would silently disable the in-network VIP retry and warning.
|
|
expect-vip: ${{ vars.RUNNER_LINUX_X64_8 != '' }}
|
|
|
|
- name: Install dependencies
|
|
if: steps.cache.outputs.cache-hit != 'true'
|
|
run: yarn install --network-timeout 1000000 --frozen-lockfile --ignore-scripts
|
|
|
|
- name: Run postinstall manually
|
|
if: steps.cache.outputs.cache-hit != 'true'
|
|
run: yarn postinstall.manual
|
|
|
|
- name: Archive node_modules
|
|
if: steps.cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
run: .github/scripts/archive-node-modules.sh
|
|
|
|
- name: Save node_modules archive
|
|
if: steps.cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v4
|
|
continue-on-error: true
|
|
# Explicit save so a FAILED install can never publish a half-built tree. BEST EFFORT, and only
|
|
# a CROSS-run optimization — see the upload below for why it cannot be the handoff.
|
|
with:
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
# Pinned to the key the RESTORE computed, not re-derived: Configure Registry rewrites
|
|
# yarn.lock after the restore, and hashFiles() here evaluates at save time - re-deriving
|
|
# would write the archive under a different key than the next run looks up.
|
|
key: ${{ steps.cache.outputs.cache-primary-key }}
|
|
|
|
- name: Upload node_modules archive
|
|
uses: actions/upload-artifact@v7
|
|
# THE HANDOFF. The cache above cannot be relied on for it: an Actions cache is a REPOSITORY
|
|
# resource on a 10 GB budget, so a build on any other ref can evict this entry between the
|
|
# save and the consumer's restore. That is not hypothetical — it happened on the first run of
|
|
# this design (run 32377379196): the producer logged "Cache saved with key: …2b58b651…",
|
|
# build-libs asked for the byte-identical key 32 seconds later and got "Failed to restore
|
|
# cache entry", because three 4.66 GB setup-node yarn caches written by develop, stage and
|
|
# PR #10014 had pushed the repo to 14.03 GB and LRU took the newest entry.
|
|
# An artifact is scoped to THIS RUN and no other workflow can evict it. The cleanup job
|
|
# deletes it when the run ends, so it costs storage only while the run needs it.
|
|
with:
|
|
name: build-node-modules
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
retention-days: 1
|
|
compression-level: 0 # already zstd-compressed
|
|
if-no-files-found: error
|
|
overwrite: true
|
|
|
|
# The strict library verdict, as its own early-reporting check: this is the task set whose
|
|
# per-library `strictTemplates` tsconfigs catch template type errors (the class that broke the
|
|
# demo webapp image). It has no `needs` edge and no app build behind it, so it reports as soon
|
|
# as install + the 71 library builds finish.
|
|
build-libs:
|
|
name: build-libs
|
|
needs: build-monorepo-root
|
|
# `needs:` alone would make a producer failure SKIP this job, and a skipped required check does
|
|
# not block a merge — the exact "no failure just means never ran" trap this file's header warns
|
|
# about. `!cancelled()` keeps the ordering but still runs on producer failure, where the restore
|
|
# fails loudly and this reports a real red instead of vanishing.
|
|
# Never run jobs for a pull request from a fork (owner decision 2026-09-16); branch PRs and pushes still run.
|
|
if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_8 || 'ubuntu-latest' }}
|
|
timeout-minutes: 180
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Download node_modules archive
|
|
id: artifact
|
|
uses: actions/download-artifact@v8
|
|
# Primary handoff — run-scoped, so no other workflow can evict it between jobs.
|
|
continue-on-error: true
|
|
with:
|
|
name: build-node-modules
|
|
|
|
- name: Restore node_modules archive (cache fallback)
|
|
# Gated, because it was NOT before and the comment below claimed otherwise. With no `if:`
|
|
# this ran even on a successful artifact download, and since the producer saves the same
|
|
# key each cold run the lookup HITS - so every consumer pulled the same ~2.9 GB twice.
|
|
# Measured on run 32418672320: artifact download 1015s/1028s/1102s, then this step still
|
|
# running past 209s on top. ~17 min of pure waste per consumer, ~68 min per run.
|
|
if: steps.artifact.outcome != 'success'
|
|
uses: actions/cache/restore@v4
|
|
continue-on-error: true
|
|
with:
|
|
# Only reached if the artifact was unavailable. Deliberately NOT fail-on-cache-miss: the
|
|
# cache is best effort here, and the script below still has the install fallback.
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-node-modules-${{ hashFiles('yarn.lock', 'package.json', 'patches/**', '.scripts/postinstall.js') }}
|
|
|
|
# Route the install through the internal Verdaccio cache. Placed AFTER the cache restore on
|
|
# purpose: this action rewrites yarn.lock's resolved URLs, and hashFiles() in a cache key
|
|
# evaluates at step runtime - configuring the registry before the restore would move the key
|
|
# and bifurcate the cache namespace by VIP reachability (the reason #10025 skipped this file).
|
|
# Measured cost of NOT having it here: the bootstrap fallback pulled from the public registry
|
|
# and blew through the 180-minute job ceiling twice on stage run 32513912629.
|
|
- name: Configure Registry
|
|
uses: ever-co/ever-gauzy/.github/actions/configure-registry@aa4ee19926fabcf820aa1294385a76aec6bdb548
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# Not contains(matrix.os, ...): these jobs define no matrix.os, so that expression is
|
|
# always false and would silently disable the in-network VIP retry and warning.
|
|
expect-vip: ${{ vars.RUNNER_LINUX_X64_8 != '' }}
|
|
|
|
- name: Restore node_modules
|
|
shell: bash
|
|
# Unpacks the tree build-monorepo-root published, or installs from scratch if the cache is
|
|
# unavailable. One step, so there is no `if:` on a previous step's outcome to get wrong.
|
|
run: .github/scripts/restore-node-modules.sh
|
|
|
|
- name: Build packages
|
|
run: yarn build:package:all
|
|
|
|
build-api:
|
|
name: build-api
|
|
needs: build-monorepo-root
|
|
# `needs:` alone would make a producer failure SKIP this job, and a skipped required check does
|
|
# not block a merge — the exact "no failure just means never ran" trap this file's header warns
|
|
# about. `!cancelled()` keeps the ordering but still runs on producer failure, where the restore
|
|
# fails loudly and this reports a real red instead of vanishing.
|
|
# Never run jobs for a pull request from a fork (owner decision 2026-09-16); branch PRs and pushes still run.
|
|
if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_8 || 'ubuntu-latest' }}
|
|
timeout-minutes: 180
|
|
services:
|
|
postgres:
|
|
image: postgres:18-alpine@sha256:d3e1620b530c944afa6e887d22eb899824da68e19c52024bf98f5220c88a65b2
|
|
env:
|
|
POSTGRES_USER: migration_test
|
|
POSTGRES_PASSWORD: migration_test
|
|
POSTGRES_DB: migration_test
|
|
ports:
|
|
- 5432/tcp
|
|
options: >-
|
|
--health-cmd "pg_isready -U migration_test -d migration_test"
|
|
--health-interval 5s --health-timeout 5s --health-retries 12
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Download node_modules archive
|
|
id: artifact
|
|
uses: actions/download-artifact@v8
|
|
# Primary handoff — run-scoped, so no other workflow can evict it between jobs.
|
|
continue-on-error: true
|
|
with:
|
|
name: build-node-modules
|
|
|
|
- name: Restore node_modules archive (cache fallback)
|
|
# Gated, because it was NOT before and the comment below claimed otherwise. With no `if:`
|
|
# this ran even on a successful artifact download, and since the producer saves the same
|
|
# key each cold run the lookup HITS - so every consumer pulled the same ~2.9 GB twice.
|
|
# Measured on run 32418672320: artifact download 1015s/1028s/1102s, then this step still
|
|
# running past 209s on top. ~17 min of pure waste per consumer, ~68 min per run.
|
|
if: steps.artifact.outcome != 'success'
|
|
uses: actions/cache/restore@v4
|
|
continue-on-error: true
|
|
with:
|
|
# Only reached if the artifact was unavailable. Deliberately NOT fail-on-cache-miss: the
|
|
# cache is best effort here, and the script below still has the install fallback.
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-node-modules-${{ hashFiles('yarn.lock', 'package.json', 'patches/**', '.scripts/postinstall.js') }}
|
|
|
|
# Route the install through the internal Verdaccio cache. Placed AFTER the cache restore on
|
|
# purpose: this action rewrites yarn.lock's resolved URLs, and hashFiles() in a cache key
|
|
# evaluates at step runtime - configuring the registry before the restore would move the key
|
|
# and bifurcate the cache namespace by VIP reachability (the reason #10025 skipped this file).
|
|
# Measured cost of NOT having it here: the bootstrap fallback pulled from the public registry
|
|
# and blew through the 180-minute job ceiling twice on stage run 32513912629.
|
|
- name: Configure Registry
|
|
uses: ever-co/ever-gauzy/.github/actions/configure-registry@aa4ee19926fabcf820aa1294385a76aec6bdb548
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# Not contains(matrix.os, ...): these jobs define no matrix.os, so that expression is
|
|
# always false and would silently disable the in-network VIP retry and warning.
|
|
expect-vip: ${{ vars.RUNNER_LINUX_X64_8 != '' }}
|
|
|
|
- name: Restore node_modules
|
|
shell: bash
|
|
# Unpacks the tree build-monorepo-root published, or installs from scratch if the cache is
|
|
# unavailable. One step, so there is no `if:` on a previous step's outcome to get wrong.
|
|
run: .github/scripts/restore-node-modules.sh
|
|
|
|
- name: Test PostgreSQL migrations
|
|
# cspell:words PGHOST PGPORT PGDATABASE
|
|
env:
|
|
PGHOST: 127.0.0.1
|
|
PGPORT: ${{ job.services.postgres.ports[5432] }}
|
|
PGUSER: migration_test
|
|
PGPASSWORD: migration_test
|
|
PGDATABASE: migration_test
|
|
run: yarn nx run core:test-postgres-migrations
|
|
|
|
- name: Test Ever Async integration boundary
|
|
run: yarn nx run plugin-integration-ever-async:test-integration
|
|
|
|
- name: Build API
|
|
run: yarn build:api:prod:ci
|
|
|
|
build-web:
|
|
name: build-web
|
|
needs: build-monorepo-root
|
|
# `needs:` alone would make a producer failure SKIP this job, and a skipped required check does
|
|
# not block a merge — the exact "no failure just means never ran" trap this file's header warns
|
|
# about. `!cancelled()` keeps the ordering but still runs on producer failure, where the restore
|
|
# fails loudly and this reports a real red instead of vanishing.
|
|
# Never run jobs for a pull request from a fork (owner decision 2026-09-16); branch PRs and pushes still run.
|
|
if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_8 || 'ubuntu-latest' }}
|
|
timeout-minutes: 180
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Download node_modules archive
|
|
id: artifact
|
|
uses: actions/download-artifact@v8
|
|
# Primary handoff — run-scoped, so no other workflow can evict it between jobs.
|
|
continue-on-error: true
|
|
with:
|
|
name: build-node-modules
|
|
|
|
- name: Restore node_modules archive (cache fallback)
|
|
# Gated, because it was NOT before and the comment below claimed otherwise. With no `if:`
|
|
# this ran even on a successful artifact download, and since the producer saves the same
|
|
# key each cold run the lookup HITS - so every consumer pulled the same ~2.9 GB twice.
|
|
# Measured on run 32418672320: artifact download 1015s/1028s/1102s, then this step still
|
|
# running past 209s on top. ~17 min of pure waste per consumer, ~68 min per run.
|
|
if: steps.artifact.outcome != 'success'
|
|
uses: actions/cache/restore@v4
|
|
continue-on-error: true
|
|
with:
|
|
# Only reached if the artifact was unavailable. Deliberately NOT fail-on-cache-miss: the
|
|
# cache is best effort here, and the script below still has the install fallback.
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-node-modules-${{ hashFiles('yarn.lock', 'package.json', 'patches/**', '.scripts/postinstall.js') }}
|
|
|
|
# Route the install through the internal Verdaccio cache. Placed AFTER the cache restore on
|
|
# purpose: this action rewrites yarn.lock's resolved URLs, and hashFiles() in a cache key
|
|
# evaluates at step runtime - configuring the registry before the restore would move the key
|
|
# and bifurcate the cache namespace by VIP reachability (the reason #10025 skipped this file).
|
|
# Measured cost of NOT having it here: the bootstrap fallback pulled from the public registry
|
|
# and blew through the 180-minute job ceiling twice on stage run 32513912629.
|
|
- name: Configure Registry
|
|
uses: ever-co/ever-gauzy/.github/actions/configure-registry@aa4ee19926fabcf820aa1294385a76aec6bdb548
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# Not contains(matrix.os, ...): these jobs define no matrix.os, so that expression is
|
|
# always false and would silently disable the in-network VIP retry and warning.
|
|
expect-vip: ${{ vars.RUNNER_LINUX_X64_8 != '' }}
|
|
|
|
- name: Restore node_modules
|
|
shell: bash
|
|
# Unpacks the tree build-monorepo-root published, or installs from scratch if the cache is
|
|
# unavailable. One step, so there is no `if:` on a previous step's outcome to get wrong.
|
|
run: .github/scripts/restore-node-modules.sh
|
|
|
|
- name: Build web
|
|
run: yarn build:gauzy:prod:ci
|
|
|
|
build-desktop:
|
|
name: build-desktop
|
|
needs: build-monorepo-root
|
|
# `needs:` alone would make a producer failure SKIP this job, and a skipped required check does
|
|
# not block a merge — the exact "no failure just means never ran" trap this file's header warns
|
|
# about. `!cancelled()` keeps the ordering but still runs on producer failure, where the restore
|
|
# fails loudly and this reports a real red instead of vanishing.
|
|
# The second half matches the CircleCI branch filter: desktop was never built on ordinary PR
|
|
# branches. Both conditions live in ONE expression — two `if:` keys is a duplicate mapping key and
|
|
# the file will not parse.
|
|
if: >-
|
|
${{ !cancelled() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/stage' ||
|
|
github.ref == 'refs/heads/master') }}
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_8 || 'ubuntu-latest' }}
|
|
timeout-minutes: 180
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Install system dependencies for Electron
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
build-essential icnsutils graphicsmagick binutils libappindicator3-1 || true
|
|
|
|
- name: Download node_modules archive
|
|
id: artifact
|
|
uses: actions/download-artifact@v8
|
|
# Primary handoff — run-scoped, so no other workflow can evict it between jobs.
|
|
continue-on-error: true
|
|
with:
|
|
name: build-node-modules
|
|
|
|
- name: Restore node_modules archive (cache fallback)
|
|
# Gated, because it was NOT before and the comment below claimed otherwise. With no `if:`
|
|
# this ran even on a successful artifact download, and since the producer saves the same
|
|
# key each cold run the lookup HITS - so every consumer pulled the same ~2.9 GB twice.
|
|
# Measured on run 32418672320: artifact download 1015s/1028s/1102s, then this step still
|
|
# running past 209s on top. ~17 min of pure waste per consumer, ~68 min per run.
|
|
if: steps.artifact.outcome != 'success'
|
|
uses: actions/cache/restore@v4
|
|
continue-on-error: true
|
|
with:
|
|
# Only reached if the artifact was unavailable. Deliberately NOT fail-on-cache-miss: the
|
|
# cache is best effort here, and the script below still has the install fallback.
|
|
path: ${{ env.NODE_MODULES_ARCHIVE }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-node-modules-${{ hashFiles('yarn.lock', 'package.json', 'patches/**', '.scripts/postinstall.js') }}
|
|
|
|
# Route the install through the internal Verdaccio cache. Placed AFTER the cache restore on
|
|
# purpose: this action rewrites yarn.lock's resolved URLs, and hashFiles() in a cache key
|
|
# evaluates at step runtime - configuring the registry before the restore would move the key
|
|
# and bifurcate the cache namespace by VIP reachability (the reason #10025 skipped this file).
|
|
# Measured cost of NOT having it here: the bootstrap fallback pulled from the public registry
|
|
# and blew through the 180-minute job ceiling twice on stage run 32513912629.
|
|
- name: Configure Registry
|
|
uses: ever-co/ever-gauzy/.github/actions/configure-registry@aa4ee19926fabcf820aa1294385a76aec6bdb548
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# Not contains(matrix.os, ...): these jobs define no matrix.os, so that expression is
|
|
# always false and would silently disable the in-network VIP retry and warning.
|
|
expect-vip: ${{ vars.RUNNER_LINUX_X64_8 != '' }}
|
|
|
|
- name: Restore node_modules
|
|
shell: bash
|
|
# Unpacks the tree build-monorepo-root published, or installs from scratch if the cache is
|
|
# unavailable. One step, so there is no `if:` on a previous step's outcome to get wrong.
|
|
run: .github/scripts/restore-node-modules.sh
|
|
|
|
- name: Build desktop
|
|
run: yarn build:desktop
|
|
|
|
# ---------------------------------------------------------------------------------------------
|
|
# cleanup — the dependency archive exists only to cross job boundaries; delete it when the run ends.
|
|
# ---------------------------------------------------------------------------------------------
|
|
cleanup:
|
|
name: Delete node_modules artifact
|
|
needs: [build-monorepo-root, build-libs, build-api, build-web, build-desktop]
|
|
# Delete only when nothing failed. This used to be a bare `always()` (rationale then: red-run
|
|
# leftovers once filled the org's Actions storage quota and stopped uploads) - but the artifact
|
|
# is this run's ONLY dependency handoff, so deleting it on a red run breaks "Re-run failed
|
|
# jobs": every rerun consumer falls into the 1-3h bootstrap fallback. Measured on stage run
|
|
# 32513912629 (2026-08-21): a seconds-long DNS blip failed one restore, cleanup deleted the
|
|
# artifact, and the rerun cost build-libs 178m and pushed build-api into the 180m ceiling.
|
|
# The quota concern is now bounded instead of ignored: red Build runs are rare since the gate
|
|
# rework, and retention-days: 1 ages a kept artifact out within a day regardless.
|
|
# `skipped` (build-desktop on PR runs) still allows deletion - only failure/cancelled block it.
|
|
# Never run jobs for a pull request from a fork (owner decision 2026-09-16); branch PRs and pushes still run.
|
|
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
actions: write # deletes this run's node_modules artifact
|
|
steps:
|
|
- name: Delete build-node-modules
|
|
shell: bash
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
ids=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts" --jq '.artifacts[] | select(.name=="build-node-modules") | .id')
|
|
if [ -z "$ids" ]; then echo "nothing to delete"; exit 0; fi
|
|
for id in $ids; do
|
|
if gh api --method DELETE "repos/$GITHUB_REPOSITORY/actions/artifacts/$id" >/dev/null 2>&1; then
|
|
echo "deleted artifact $id"
|
|
else
|
|
echo "::warning::could not delete artifact $id — already gone?"
|
|
fi
|
|
done
|