mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
Every Gauzy app snap (desktop, desktop-timer, server, api-server, agent, mcp-server; amd64 and arm64) went to the Snap Store 'edge' channel only, from both the stage lane (stage-apps) and the prod lane (apps). The electron-builder snap target publishes with its own snapStore config. When the build config has no snapStore entry, that config has no channels, and the Snap Store publisher then defaults to 'edge'. Prod builds now release to 'stable' and stage builds to 'edge'. Each Linux build step appends -c.snap.publish.provider=snapStore -c.snap.publish.channels=<channel> to its yarn command. yarn adds extra arguments to the end of the script, which is the electron-builder call. The override is in the workflows because the root build scripts are shared by both lanes. Only snap.publish is overridden: a -c.publish override would be merged into the GitHub publish entry. GitHub releases, update channels and every other target are unchanged. Stage names 'edge' explicitly so each prod/stage pair still differs only in the channel. The generated snaps already use grade stable and strict confinement, and they need no store-approved plugs, so the store accepts them on stable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1430 lines
75 KiB
YAML
1430 lines
75 KiB
YAML
name: API Server Build Prod
|
|
|
|
# The packaged desktop & server apps are built ONLY when 'master' is promoted to the 'apps' branch
|
|
# (branch flow: develop -> stage -> master -> apps).
|
|
# The build version is resolved at build time (.scripts/bump-version-electron.js) from the release
|
|
# tag of the promoted commit (on HEAD, or on the merge parent for PR-merge promotions), which
|
|
# 'Release Prod' creates on the merge to 'master' - so app releases
|
|
# always carry the same version as the corresponding platform release and publish to the
|
|
# same targets (each app repo's GitHub Releases + DigitalOcean Spaces).
|
|
on:
|
|
push:
|
|
branches:
|
|
- apps
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ${{ github.ref }}-${{ github.workflow }}
|
|
cancel-in-progress: true
|
|
|
|
# Least-privilege scope for the automatic GITHUB_TOKEN.
|
|
# This workflow publishes its release assets with the separate `secrets.GH_TOKEN` PAT,
|
|
# which this block does not affect, so the automatic token only needs to read the repo.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
check-release-tag:
|
|
# Only build when the promoted commit carries a release tag (the version stamped into the
|
|
# packages - see header comment). 'apps' is promoted from 'master' either by
|
|
# fast-forwarding to the tagged 'master' commit (tag on HEAD) or by merging the
|
|
# 'master' -> 'apps' promotion PR (the tag then points at the merged
|
|
# 'master' tip, HEAD^2). Retries absorb the short delay until 'Release Prod' tags
|
|
# the 'master' commit.
|
|
runs-on: ${{ vars.RUNNER_LINUX_X64_4 || 'ubuntu-latest' }}
|
|
timeout-minutes: 30
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
# The resolved vX.Y.Z release tag; the build jobs stamp exactly this version.
|
|
tag: ${{ steps.resolve.outputs.tag }}
|
|
steps:
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@v5
|
|
with:
|
|
persist-credentials: false
|
|
# Depth 2 so HEAD^2 resolves on merge-commit promotions
|
|
fetch-depth: 2
|
|
|
|
- name: Verify a release tag points at the promoted commit
|
|
id: resolve
|
|
shell: bash
|
|
run: |
|
|
if [ "$GITHUB_REF_NAME" != "apps" ]; then
|
|
echo "::error::This workflow only releases from the 'apps' branch (got '$GITHUB_REF_NAME')."
|
|
exit 1
|
|
fi
|
|
HEAD_SHA=$(git rev-parse HEAD)
|
|
# Present only when the promotion PR was merged as a merge commit; the release tag
|
|
# then points at the merged 'master' tip, not at the merge commit itself.
|
|
PARENT2_SHA=$(git rev-parse --verify --quiet 'HEAD^2' || true)
|
|
resolve_tag() {
|
|
# Highest vX.Y.Z tag pointing at $1 in the remote listing (peeled '^{}' entries
|
|
# carry the commit sha of annotated tags); empty when none match.
|
|
printf '%s\n' "$REMOTE_REFS" | awk -v sha="$1" '
|
|
$1 == sha && $2 ~ /^refs\/tags\/v[0-9]+\.[0-9]+\.[0-9]+(\^\{\})?$/ {
|
|
t = $2
|
|
sub(/^refs\/tags\//, "", t)
|
|
sub(/\^\{\}$/, "", t)
|
|
print t
|
|
}' | sort -V | tail -n 1
|
|
}
|
|
for i in $(seq 1 20); do
|
|
if REMOTE_REFS=$(git ls-remote origin refs/heads/master 'refs/tags/*'); then
|
|
SRC_TIP=$(printf '%s\n' "$REMOTE_REFS" | awk -v ref="refs/heads/master" '$2 == ref { print $1 }')
|
|
TAG=$(resolve_tag "$HEAD_SHA")
|
|
# Accept the merge-parent tag only when HEAD^2 is the current 'master' tip -
|
|
# i.e. this is the promotion merge of 'master', not an arbitrary tagged branch
|
|
# merged in, nor a fast-forward racing 'Release Prod' (whose tag lands on HEAD).
|
|
if [ -z "$TAG" ] && [ -n "$PARENT2_SHA" ] && [ "$PARENT2_SHA" = "$SRC_TIP" ]; then
|
|
TAG=$(resolve_tag "$PARENT2_SHA")
|
|
fi
|
|
if [ -n "$TAG" ]; then
|
|
echo "Release tag on the promoted commit: $TAG"
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
else
|
|
echo "git ls-remote failed (attempt $i); will retry"
|
|
fi
|
|
echo "No release tag points at this commit or its merge parent yet (attempt $i/20); retrying in 30s..."
|
|
sleep 30
|
|
done
|
|
echo "::error::No release tag points at this commit, and its merge parent does not match the tagged 'master' tip. Promote by merging the 'master' -> 'apps' PR (or fast-forwarding: git push origin origin/master:apps) after 'Release Prod' has created the tag; if 'master' has moved since the promotion PR was opened, re-promote."
|
|
exit 1
|
|
|
|
release-linux:
|
|
needs: check-release-tag
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 300
|
|
|
|
strategy:
|
|
matrix:
|
|
# Flatpak (bwrap) and Snapcraft (snapd) cannot run inside the k8s ARC container
|
|
# runners - this packaging job needs a VM-class runner. Override with the
|
|
# RUNNER_LINUX_APPS_X64 org/repo variable to use a self-hosted VM.
|
|
os: ["${{ vars.RUNNER_LINUX_APPS_X64 || 'ubuntu-latest' }}"]
|
|
|
|
steps:
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Install Node.js, NPM and Yarn
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24.17.0
|
|
|
|
- name: Get yarn cache directory path
|
|
id: yarn-cache-dir-path
|
|
shell: bash
|
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
|
|
- uses: actions/cache@v5
|
|
id: yarn-cache
|
|
with:
|
|
path: |
|
|
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
.nx/cache
|
|
key: ${{ runner.os }}-${{ runner.arch }}-yarn-nx-${{ hashFiles('yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-nx-
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-
|
|
|
|
- name: Change permissions
|
|
run: 'sudo chown -R $(whoami) ./*'
|
|
|
|
- name: Install system dependencies
|
|
run: 'sudo apt-get update && sudo env DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt install -y curl gnupg git libappindicator3-1 ca-certificates binutils icnsutils graphicsmagick flatpak flatpak-builder'
|
|
|
|
- name: Initialize Flatpak
|
|
run: |
|
|
export XDG_DATA_DIRS=$XDG_DATA_DIRS:/var/lib/flatpak/exports/share:$HOME/.local/share/flatpak/exports/share
|
|
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
|
flatpak install --user -y --noninteractive flathub \
|
|
org.freedesktop.Platform//24.08 \
|
|
org.freedesktop.Sdk//24.08 \
|
|
org.electronjs.Electron2.BaseApp//24.08
|
|
|
|
- name: Install Snapcraft
|
|
# Pin snapcraft 7.x: 8.0+ renamed the `snap` command to `pack`, but electron-builder's
|
|
# app-builder still invokes `snapcraft snap` (ERR_ELECTRON_BUILDER_CANNOT_EXECUTE). 7.x keeps
|
|
# the `snap` command and still supports the core22 base.
|
|
run: sudo snap install snapcraft --classic --channel=7.x/stable
|
|
|
|
- name: Use Python 3.11 for native rebuilds (Linux)
|
|
# node-gyp's gyp eval()-parses Electron 38's common.gypi; Python 3.12's stricter tokenizer
|
|
# rejects it ("unterminated string literal"), breaking better-sqlite3's source rebuild
|
|
# (no prebuilt exists for Electron 38's ABI). Python 3.11 parses it fine. Pinned to a SHA.
|
|
id: py311
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Fix node-gyp and Python
|
|
# Install build deps into, and point node-gyp at, the SAME 3.11 interpreter (not a stray python3).
|
|
run: |
|
|
"${{ steps.py311.outputs.python-path }}" -m pip install packaging setuptools
|
|
echo "npm_config_python=${{ steps.py311.outputs.python-path }}" >> "$GITHUB_ENV"
|
|
echo "PYTHON=${{ steps.py311.outputs.python-path }}" >> "$GITHUB_ENV"
|
|
|
|
- name: Install latest version of NPM
|
|
run: 'sudo npm install -g npm@11.6.2'
|
|
|
|
- name: Install globally node-gyp, ts-node and nx packages
|
|
run: 'sudo npm install --quiet -g node-gyp@10.2.0 ts-node@10.9.2 nx@^22.5.2'
|
|
|
|
- name: Configure Registry
|
|
uses: ./.github/actions/configure-registry
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# in-network runners: the self-hosted Windows boxes, plus any ever-k8s-* ARC pool a runner
|
|
# variable may select (release-linux takes its os from vars.RUNNER_LINUX_APPS_X64). Keep the
|
|
# ever-k8s disjunct even where a matrix cannot currently emit that label - it costs nothing,
|
|
# keeps all 66 call sites identical, and means a future ARC matrix entry inherits the VIP
|
|
# retry and the in-network warning instead of silently losing them.
|
|
expect-vip: ${{ contains(matrix.os, 'self-hosted') || contains(matrix.os, 'ever-k8s') }}
|
|
|
|
- name: Install Yarn dependencies
|
|
run: 'yarn install --network-timeout 1000000 --frozen-lockfile --ignore-scripts'
|
|
|
|
- name: Run Postinstall Manually
|
|
run: 'yarn postinstall.manual'
|
|
|
|
- name: Bump server version
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const script = require('./.scripts/bump-version-electron.js')
|
|
console.log(script.serverapi(true))
|
|
env:
|
|
GAUZY_RELEASE_TAG: ${{ needs.check-release-tag.outputs.tag }}
|
|
PROJECT_REPO: 'https://github.com/ever-co/ever-gauzy.git'
|
|
DESKTOP_API_SERVER_APP_NAME: 'gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_NAME: 'ever-gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_OWNER: 'ever-co'
|
|
COMPANY_SITE_LINK: 'https://gauzy.co'
|
|
DESKTOP_API_SERVER_APP_DESCRIPTION: 'Gauzy API Server'
|
|
DESKTOP_API_SERVER_APP_ID: 'com.ever.gauzyapiserver'
|
|
|
|
- name: Ensure dist directory exists
|
|
shell: bash
|
|
run: mkdir -p dist/packages
|
|
|
|
- name: Build Server
|
|
# Snap Store channel: prod ('apps') releases to 'stable', stage ('stage-apps') to 'edge' (the
|
|
# electron-builder default). yarn appends these flags to the script's last command, the
|
|
# electron-builder call. Override snap.publish only: a -c.publish override lands in the GitHub entry.
|
|
run: 'yarn build:gauzy-api-server:linux:release:gh:x64 -c.snap.publish.provider=snapStore -c.snap.publish.channels=stable'
|
|
env:
|
|
USE_HARD_LINKS: false
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
EP_GH_IGNORE_TIME: true
|
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
|
|
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
|
|
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
|
|
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
|
|
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
|
|
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
|
NX_NO_CLOUD: true
|
|
NX_DAEMON: false
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
|
|
|
- name: Scrub registry credentials
|
|
if: always()
|
|
shell: bash
|
|
# DELIBERATELY INLINE, not a composite action. A local action is resolved from the
|
|
# workspace, so a failed checkout means it cannot load and this step errors instead of
|
|
# running. 30 of these jobs check out with `clean: false`, where the previous run's
|
|
# workspace - and any live _authToken in it - survives; that is the exact case this step
|
|
# exists to cover. Configure Registry is a composite action because it genuinely needs the checkout.
|
|
run: |
|
|
# The auth token must not outlive the job. These runners check out with clean: false and
|
|
# clean only dist/ and node_modules/, so a workspace .npmrc carrying
|
|
# //packages.ever.co/:_authToken=... would sit on disk after the job ends - readable by
|
|
# anything scheduled on this runner before the next Configure Registry step resets it.
|
|
#
|
|
# This is deliberately the LAST step of the job: the build steps above run
|
|
# postinstall.electron / electron-builder install-app-deps, which resolve dependencies,
|
|
# so the credential has to survive until they are done. Only the credential lines go;
|
|
# the registry= line stays. Runs on failure too, which is when it would linger.
|
|
#
|
|
# No 'sed -i.bak': the backup would itself hold the token if this step were interrupted.
|
|
# The temp file only ever holds the SCRUBBED content, so a partial run leaks nothing.
|
|
# Cleanup policy, precisely:
|
|
# * individual cleanup ATTEMPTS are best-effort. Under `set -e` a failing sed or mv
|
|
# would abort this step before the token was removed - the exact outcome the step
|
|
# exists to prevent - so nothing is allowed to short-circuit it.
|
|
# * the POSTCONDITION is not best-effort. A surviving CREDENTIAL fails the step, because
|
|
# handing a live token to the next job on a reused clean: false runner is worse than a
|
|
# red build. Leftover registry STATE (a stale .yarnrc, a yarn.lock still rewritten to
|
|
# the VIP) only warns: it is a correctness nuisance for an unrelated workflow, not a
|
|
# secret, and the next Configure Registry step resets it anyway.
|
|
# Anything that cannot be DETERMINED counts as dirty, so an unreadable file is never
|
|
# mistaken for a clean one (grep exits 2 on a read error, which is not "no token").
|
|
set +e
|
|
|
|
# Restoring the tracked files from git is the primary mechanism: it reverts the whole
|
|
# file, so the credential, the appended registry= line and the yarn.lock rewrite all go
|
|
# in one operation.
|
|
git checkout -- .npmrc yarn.lock 2>/dev/null
|
|
# Fallback for a workspace where git cannot run at all.
|
|
if [ -f .npmrc ]; then
|
|
sed -e '/_authToken=/d' -e '/always-auth=/d' .npmrc > .npmrc.scrubbed 2>/dev/null && mv -f .npmrc.scrubbed .npmrc
|
|
fi
|
|
rm -f .npmrc.bak .npmrc.scrubbed .yarnrc yarn.lock.bak yarn.lock.rewritten
|
|
|
|
# Prove the credential is gone, starting from "undetermined" rather than "absent" so no
|
|
# inconclusive result can pass. Two ways to be inconclusive: grep exits 2 when a file
|
|
# cannot be READ, and [ -f ] answers false for both "missing" and "cannot stat", so a
|
|
# bare existence test cannot tell an absent file from an unreachable one.
|
|
cred_state="undetermined"
|
|
if [ -e .npmrc ] || [ -L .npmrc ]; then
|
|
grep -q '_authToken=' .npmrc
|
|
case "$?" in
|
|
0) cred_state="present" ;;
|
|
1) cred_state="absent" ;;
|
|
*) cred_state="undetermined" ;;
|
|
esac
|
|
elif [ -r . ] && [ -x . ]; then
|
|
# The directory is both readable AND searchable and neither a file nor a symlink named
|
|
# .npmrc exists, so the absence is proven rather than merely unobservable. Without the
|
|
# -x test a stat could fail in a directory that still answers -r, and without the -L
|
|
# test above a dangling symlink would read as "missing" while its target held a token.
|
|
cred_state="absent"
|
|
fi
|
|
# Artifacts that can also carry the token: .npmrc.bak is written by older revisions of
|
|
# this workflow, and a surviving .npmrc.scrubbed means the mv above did not complete.
|
|
# Present-but-clean is only clutter; present-and-carrying-a-token (or unreadable) is not.
|
|
for stray in .npmrc.bak .npmrc.scrubbed; do
|
|
if [ -e "$stray" ] || [ -L "$stray" ]; then
|
|
grep -q '_authToken=' "$stray"
|
|
if [ "$?" -ne 1 ]; then
|
|
cred_state="present in $stray"
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$cred_state" != "absent" ]; then
|
|
echo "::error title=Registry credential may still be present::Auth token is $cred_state after cleanup on ${RUNNER_NAME:-this runner}."
|
|
exit 1
|
|
fi
|
|
|
|
# Report - but do not fail on - leftover registry state.
|
|
leftover=""
|
|
[ -e .yarnrc ] && leftover="$leftover .yarnrc"
|
|
git diff --quiet -- .npmrc yarn.lock 2>/dev/null
|
|
case "$?" in
|
|
0) ;;
|
|
1) leftover="$leftover .npmrc/yarn.lock(modified)" ;;
|
|
*) leftover="$leftover .npmrc/yarn.lock(unverifiable)" ;;
|
|
esac
|
|
if [ -n "$leftover" ]; then
|
|
echo "::warning title=Registry state left behind::Cleanup could not fully restore:$leftover on ${RUNNER_NAME:-this runner}. The next Configure Registry step resets it, but a job from another workflow could inherit it first."
|
|
else
|
|
echo "Registry credential removed; .npmrc, .yarnrc and yarn.lock restored to HEAD."
|
|
fi
|
|
release-linux-arm64:
|
|
needs: check-release-tag
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 300
|
|
|
|
strategy:
|
|
matrix:
|
|
os: ["${{ vars.RUNNER_LINUX_ARM64 || 'ubuntu-24.04-arm' }}"]
|
|
|
|
steps:
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Install Node.js, NPM and Yarn
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24.17.0
|
|
|
|
- name: Get yarn cache directory path
|
|
id: yarn-cache-dir-path
|
|
shell: bash
|
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
|
|
- uses: actions/cache@v5
|
|
id: yarn-cache
|
|
with:
|
|
path: |
|
|
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
.nx/cache
|
|
key: ${{ runner.os }}-${{ runner.arch }}-yarn-nx-${{ hashFiles('yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-nx-
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-
|
|
|
|
- name: Change permissions
|
|
run: 'sudo chown -R $(whoami) ./*'
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo env DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt install -y curl gnupg git libappindicator3-1 ca-certificates binutils icnsutils graphicsmagick libx11-dev libxtst-dev libxt-dev libxinerama-dev libx11-xcb-dev libxkbcommon-dev libxkbcommon-x11-dev libxkbfile-dev libxrandr-dev ruby ruby-dev rubygems build-essential flatpak flatpak-builder
|
|
sudo gem install --no-document fpm
|
|
|
|
- name: Initialize Flatpak
|
|
run: |
|
|
export XDG_DATA_DIRS=$XDG_DATA_DIRS:/var/lib/flatpak/exports/share:$HOME/.local/share/flatpak/exports/share
|
|
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
|
flatpak install -y --user --noninteractive flathub \
|
|
org.freedesktop.Platform//24.08 \
|
|
org.freedesktop.Sdk//24.08 \
|
|
org.electronjs.Electron2.BaseApp//24.08
|
|
|
|
- name: Install Snapcraft
|
|
# Pin snapcraft 7.x: 8.0+ renamed the `snap` command to `pack`, but electron-builder's
|
|
# app-builder still invokes `snapcraft snap` (ERR_ELECTRON_BUILDER_CANNOT_EXECUTE). 7.x keeps
|
|
# the `snap` command and still supports the core22 base.
|
|
run: sudo snap install snapcraft --classic --channel=7.x/stable
|
|
|
|
# Pre-install snapcraft's build snaps WITH sudo. In host (destructive) mode snapcraft installs
|
|
# any build snap it is missing by running `snap install` as the unprivileged runner user, and
|
|
# on the arm64 images snapd intermittently refuses that: "error: access denied (try with sudo)"
|
|
# -> "Error installing snap 'gnome-3-28-1804'" -> ERR_ELECTRON_BUILDER_CANNOT_EXECUTE. Because
|
|
# it is intermittent, arm64 snap passed in May 2026, failed from June, and on 2026-09-23 failed
|
|
# and passed within the same hour with no change. With these already present snapcraft performs
|
|
# ZERO snap installs of its own, so the failing operation is never reached. This exact list was
|
|
# proven on branch exp/arm64-snap-snapcraft-version (runs 35895302909, 35895798132, 35896229973).
|
|
- name: Pre-install snapcraft build snaps (arm64)
|
|
run: sudo snap install core18 core20 core22 gtk-common-themes gnome-3-28-1804 gnome-42-2204
|
|
|
|
# On arm64 electron-builder has no template snap, so it builds WITHOUT one: snapcraft pulls
|
|
# stage-packages, and in host (destructive) mode that runs a bare `apt-get update`. As the
|
|
# runner user that fails with "Could not open lock file /var/lib/apt/lists/lock - open (13:
|
|
# Permission denied)" -> "Failed to refresh package list: failed to run apt update." (amd64 never
|
|
# hits this: it uses the template snap and runs no apt). This shim, first on PATH, runs ONLY
|
|
# snapcraft as root and then hands the files it wrote back to the runner user.
|
|
- name: Run snapcraft as root (arm64 host-mode snap)
|
|
run: |
|
|
shim_dir="$HOME/.local/snapcraft-root-shim"
|
|
mkdir -p "$shim_dir"
|
|
cat > "$shim_dir/snapcraft" <<'SH'
|
|
#!/bin/bash
|
|
sudo --preserve-env env PATH="$PATH" /snap/bin/snapcraft "$@"
|
|
rc=$?
|
|
sudo chown -R "$(id -u):$(id -g)" "$PWD" "$HOME/.cache" "$HOME/.local/state" 2>/dev/null || true
|
|
exit $rc
|
|
SH
|
|
chmod +x "$shim_dir/snapcraft"
|
|
echo "$shim_dir" >> "$GITHUB_PATH"
|
|
|
|
- name: Install Multipass
|
|
run: 'sudo snap install multipass'
|
|
|
|
- name: Use Python 3.11 for native rebuilds (Linux)
|
|
# node-gyp's gyp eval()-parses Electron 38's common.gypi; Python 3.12's stricter tokenizer
|
|
# rejects it ("unterminated string literal"), breaking better-sqlite3's source rebuild
|
|
# (no prebuilt exists for Electron 38's ABI). Python 3.11 parses it fine. Pinned to a SHA.
|
|
id: py311
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Fix node-gyp and Python
|
|
# Install build deps into, and point node-gyp at, the SAME 3.11 interpreter (not a stray python3).
|
|
run: |
|
|
"${{ steps.py311.outputs.python-path }}" -m pip install packaging setuptools
|
|
echo "npm_config_python=${{ steps.py311.outputs.python-path }}" >> "$GITHUB_ENV"
|
|
echo "PYTHON=${{ steps.py311.outputs.python-path }}" >> "$GITHUB_ENV"
|
|
|
|
- name: Install latest version of NPM
|
|
run: 'sudo npm install -g npm@11.6.2'
|
|
|
|
- name: Install globally node-gyp, ts-node and nx packages
|
|
run: 'sudo npm install --quiet -g node-gyp@10.2.0 ts-node@10.9.2 nx@^22.5.2'
|
|
|
|
- name: Configure Registry
|
|
uses: ./.github/actions/configure-registry
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# in-network runners: the self-hosted Windows boxes, plus any ever-k8s-* ARC pool a runner
|
|
# variable may select (release-linux takes its os from vars.RUNNER_LINUX_APPS_X64). Keep the
|
|
# ever-k8s disjunct even where a matrix cannot currently emit that label - it costs nothing,
|
|
# keeps all 66 call sites identical, and means a future ARC matrix entry inherits the VIP
|
|
# retry and the in-network warning instead of silently losing them.
|
|
expect-vip: ${{ contains(matrix.os, 'self-hosted') || contains(matrix.os, 'ever-k8s') }}
|
|
|
|
- name: Install Yarn dependencies
|
|
run: 'yarn install --network-timeout 1000000 --frozen-lockfile --ignore-scripts'
|
|
|
|
- name: Run Postinstall Manually
|
|
run: 'yarn postinstall.manual'
|
|
|
|
- name: Bump version server api app
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const script = require('./.scripts/bump-version-electron.js')
|
|
console.log(script.serverapi(true))
|
|
env:
|
|
GAUZY_RELEASE_TAG: ${{ needs.check-release-tag.outputs.tag }}
|
|
PROJECT_REPO: 'https://github.com/ever-co/ever-gauzy.git'
|
|
DESKTOP_API_SERVER_APP_NAME: 'gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_NAME: 'ever-gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_OWNER: 'ever-co'
|
|
COMPANY_SITE_LINK: 'https://gauzy.co'
|
|
DESKTOP_API_SERVER_APP_DESCRIPTION: 'Gauzy API Server'
|
|
DESKTOP_API_SERVER_APP_ID: 'com.ever.gauzyapiserver'
|
|
|
|
- name: Ensure dist directory exists
|
|
shell: bash
|
|
run: mkdir -p dist/packages
|
|
|
|
- name: Build Server API
|
|
# Snap Store channel: prod ('apps') releases to 'stable', stage ('stage-apps') to 'edge' (the
|
|
# electron-builder default). yarn appends these flags to the script's last command, the
|
|
# electron-builder call. Override snap.publish only: a -c.publish override lands in the GitHub entry.
|
|
run: 'yarn build:gauzy-api-server:linux:release:gh:arm64 -c.snap.publish.provider=snapStore -c.snap.publish.channels=stable'
|
|
env:
|
|
USE_HARD_LINKS: false
|
|
USE_SYSTEM_FPM: true
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
EP_GH_IGNORE_TIME: true
|
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
|
|
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
|
|
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
|
|
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
|
|
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
|
|
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
|
NX_NO_CLOUD: true
|
|
NX_DAEMON: false
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
|
SNAPCRAFT_BUILD_ENVIRONMENT: host
|
|
|
|
- name: Scrub registry credentials
|
|
if: always()
|
|
shell: bash
|
|
# DELIBERATELY INLINE, not a composite action. A local action is resolved from the
|
|
# workspace, so a failed checkout means it cannot load and this step errors instead of
|
|
# running. 30 of these jobs check out with `clean: false`, where the previous run's
|
|
# workspace - and any live _authToken in it - survives; that is the exact case this step
|
|
# exists to cover. Configure Registry is a composite action because it genuinely needs the checkout.
|
|
run: |
|
|
# The auth token must not outlive the job. These runners check out with clean: false and
|
|
# clean only dist/ and node_modules/, so a workspace .npmrc carrying
|
|
# //packages.ever.co/:_authToken=... would sit on disk after the job ends - readable by
|
|
# anything scheduled on this runner before the next Configure Registry step resets it.
|
|
#
|
|
# This is deliberately the LAST step of the job: the build steps above run
|
|
# postinstall.electron / electron-builder install-app-deps, which resolve dependencies,
|
|
# so the credential has to survive until they are done. Only the credential lines go;
|
|
# the registry= line stays. Runs on failure too, which is when it would linger.
|
|
#
|
|
# No 'sed -i.bak': the backup would itself hold the token if this step were interrupted.
|
|
# The temp file only ever holds the SCRUBBED content, so a partial run leaks nothing.
|
|
# Cleanup policy, precisely:
|
|
# * individual cleanup ATTEMPTS are best-effort. Under `set -e` a failing sed or mv
|
|
# would abort this step before the token was removed - the exact outcome the step
|
|
# exists to prevent - so nothing is allowed to short-circuit it.
|
|
# * the POSTCONDITION is not best-effort. A surviving CREDENTIAL fails the step, because
|
|
# handing a live token to the next job on a reused clean: false runner is worse than a
|
|
# red build. Leftover registry STATE (a stale .yarnrc, a yarn.lock still rewritten to
|
|
# the VIP) only warns: it is a correctness nuisance for an unrelated workflow, not a
|
|
# secret, and the next Configure Registry step resets it anyway.
|
|
# Anything that cannot be DETERMINED counts as dirty, so an unreadable file is never
|
|
# mistaken for a clean one (grep exits 2 on a read error, which is not "no token").
|
|
set +e
|
|
|
|
# Restoring the tracked files from git is the primary mechanism: it reverts the whole
|
|
# file, so the credential, the appended registry= line and the yarn.lock rewrite all go
|
|
# in one operation.
|
|
git checkout -- .npmrc yarn.lock 2>/dev/null
|
|
# Fallback for a workspace where git cannot run at all.
|
|
if [ -f .npmrc ]; then
|
|
sed -e '/_authToken=/d' -e '/always-auth=/d' .npmrc > .npmrc.scrubbed 2>/dev/null && mv -f .npmrc.scrubbed .npmrc
|
|
fi
|
|
rm -f .npmrc.bak .npmrc.scrubbed .yarnrc yarn.lock.bak yarn.lock.rewritten
|
|
|
|
# Prove the credential is gone, starting from "undetermined" rather than "absent" so no
|
|
# inconclusive result can pass. Two ways to be inconclusive: grep exits 2 when a file
|
|
# cannot be READ, and [ -f ] answers false for both "missing" and "cannot stat", so a
|
|
# bare existence test cannot tell an absent file from an unreachable one.
|
|
cred_state="undetermined"
|
|
if [ -e .npmrc ] || [ -L .npmrc ]; then
|
|
grep -q '_authToken=' .npmrc
|
|
case "$?" in
|
|
0) cred_state="present" ;;
|
|
1) cred_state="absent" ;;
|
|
*) cred_state="undetermined" ;;
|
|
esac
|
|
elif [ -r . ] && [ -x . ]; then
|
|
# The directory is both readable AND searchable and neither a file nor a symlink named
|
|
# .npmrc exists, so the absence is proven rather than merely unobservable. Without the
|
|
# -x test a stat could fail in a directory that still answers -r, and without the -L
|
|
# test above a dangling symlink would read as "missing" while its target held a token.
|
|
cred_state="absent"
|
|
fi
|
|
# Artifacts that can also carry the token: .npmrc.bak is written by older revisions of
|
|
# this workflow, and a surviving .npmrc.scrubbed means the mv above did not complete.
|
|
# Present-but-clean is only clutter; present-and-carrying-a-token (or unreadable) is not.
|
|
for stray in .npmrc.bak .npmrc.scrubbed; do
|
|
if [ -e "$stray" ] || [ -L "$stray" ]; then
|
|
grep -q '_authToken=' "$stray"
|
|
if [ "$?" -ne 1 ]; then
|
|
cred_state="present in $stray"
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$cred_state" != "absent" ]; then
|
|
echo "::error title=Registry credential may still be present::Auth token is $cred_state after cleanup on ${RUNNER_NAME:-this runner}."
|
|
exit 1
|
|
fi
|
|
|
|
# Report - but do not fail on - leftover registry state.
|
|
leftover=""
|
|
[ -e .yarnrc ] && leftover="$leftover .yarnrc"
|
|
git diff --quiet -- .npmrc yarn.lock 2>/dev/null
|
|
case "$?" in
|
|
0) ;;
|
|
1) leftover="$leftover .npmrc/yarn.lock(modified)" ;;
|
|
*) leftover="$leftover .npmrc/yarn.lock(unverifiable)" ;;
|
|
esac
|
|
if [ -n "$leftover" ]; then
|
|
echo "::warning title=Registry state left behind::Cleanup could not fully restore:$leftover on ${RUNNER_NAME:-this runner}. The next Configure Registry step resets it, but a job from another workflow could inherit it first."
|
|
else
|
|
echo "Registry credential removed; .npmrc, .yarnrc and yarn.lock restored to HEAD."
|
|
fi
|
|
release-mac:
|
|
needs: check-release-tag
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 300
|
|
|
|
strategy:
|
|
matrix:
|
|
os: [ghcr.io/cirruslabs/macos-runner:tahoe]
|
|
|
|
steps:
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@v5
|
|
|
|
- name: Install Node.js, NPM and Yarn
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24.17.0
|
|
|
|
- name: Get yarn cache directory path
|
|
id: yarn-cache-dir-path
|
|
shell: bash
|
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
|
|
- uses: actions/cache@v5
|
|
id: yarn-cache
|
|
with:
|
|
path: |
|
|
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
.nx/cache
|
|
key: ${{ runner.os }}-${{ runner.arch }}-yarn-nx-${{ hashFiles('yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-nx-
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-
|
|
|
|
- name: Fix node-gyp and Python
|
|
run: python3 -m pip install --break-system-packages packaging setuptools || python3 -m pip install packaging setuptools
|
|
|
|
- name: Install latest version of NPM
|
|
run: 'sudo npm install -g npm@11.6.2'
|
|
|
|
- name: Install globally node-gyp, ts-node and nx packages
|
|
run: 'sudo npm install --quiet -g node-gyp@10.2.0 ts-node@10.9.2 nx@^22.5.2'
|
|
|
|
- name: Configure Registry
|
|
uses: ./.github/actions/configure-registry
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# in-network runners: the self-hosted Windows boxes, plus any ever-k8s-* ARC pool a runner
|
|
# variable may select (release-linux takes its os from vars.RUNNER_LINUX_APPS_X64). Keep the
|
|
# ever-k8s disjunct even where a matrix cannot currently emit that label - it costs nothing,
|
|
# keeps all 66 call sites identical, and means a future ARC matrix entry inherits the VIP
|
|
# retry and the in-network warning instead of silently losing them.
|
|
expect-vip: ${{ contains(matrix.os, 'self-hosted') || contains(matrix.os, 'ever-k8s') }}
|
|
|
|
- name: Install Yarn dependencies
|
|
run: 'yarn install --network-timeout 1000000 --frozen-lockfile --ignore-scripts'
|
|
|
|
- name: Run Postinstall Manually
|
|
run: 'yarn postinstall.manual'
|
|
|
|
- name: Bump Server version
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const script = require('./.scripts/bump-version-electron.js')
|
|
console.log(script.serverapi(true))
|
|
env:
|
|
GAUZY_RELEASE_TAG: ${{ needs.check-release-tag.outputs.tag }}
|
|
PROJECT_REPO: 'https://github.com/ever-co/ever-gauzy.git'
|
|
DESKTOP_API_SERVER_APP_NAME: 'gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_NAME: 'ever-gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_OWNER: 'ever-co'
|
|
COMPANY_SITE_LINK: 'https://gauzy.co'
|
|
DESKTOP_API_SERVER_APP_DESCRIPTION: 'Gauzy API Server'
|
|
DESKTOP_API_SERVER_APP_ID: 'com.ever.gauzyapiserver'
|
|
|
|
- name: Prepare Apple API Key
|
|
run: |
|
|
echo "${{ secrets.APPLE_API_KEY_BASE64 }}" | base64 --decode > /tmp/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8
|
|
chmod 600 /tmp/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8
|
|
|
|
- name: Ensure dist directory exists
|
|
shell: bash
|
|
run: mkdir -p dist/packages
|
|
|
|
# macOS signing: build the keychain ourselves instead of letting electron-builder do it.
|
|
# app-builder-lib passes the p12 password to `security set-key-partition-list -k`, which
|
|
# expects the KEYCHAIN password — fatal on the current runner image, and still unfixed in
|
|
# the latest release. With CSC_LINK unset, electron-builder skips its own keychain code and
|
|
# uses CSC_KEYCHAIN (macPackager.js:25-48), so this sidesteps the bug without patching deps.
|
|
- name: Import Apple signing certificate into a keychain
|
|
env:
|
|
CSC_LINK_BASE64: ${{ secrets.CSC_LINK_BASE64 }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
KEYCHAIN="$RUNNER_TEMP/ever-signing.keychain-db"
|
|
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"
|
|
CERT="$RUNNER_TEMP/ever-signing-cert.p12"
|
|
printf '%s' "$CSC_LINK_BASE64" | base64 --decode > "$CERT"
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
|
security set-keychain-settings -lut 21600 "$KEYCHAIN"
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
|
|
security import "$CERT" -k "$KEYCHAIN" -P "$CSC_KEY_PASSWORD" \
|
|
-T /usr/bin/codesign -T /usr/bin/productbuild -T /usr/bin/security
|
|
# The KEYCHAIN password here — this is the exact call app-builder-lib gets wrong.
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s \
|
|
-k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" > /dev/null
|
|
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | xargs)
|
|
rm -f "$CERT"
|
|
# Fail loudly here rather than silently shipping an unsigned app later.
|
|
security find-identity -v -p codesigning "$KEYCHAIN" | tee /tmp/identities.txt
|
|
grep -q "Developer ID Application" /tmp/identities.txt
|
|
|
|
- name: Build Server
|
|
run: 'yarn build:gauzy-api-server:mac:release'
|
|
env:
|
|
USE_HARD_LINKS: false
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
EP_GH_IGNORE_TIME: true
|
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
|
|
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
|
|
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
|
|
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
|
|
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
|
|
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
|
|
NX_NO_CLOUD: true
|
|
NX_DAEMON: false
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_ID_APP_PASSWORD: ${{ secrets.APPLE_ID_APP_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
# CSC_LINK deliberately NOT set: that is what makes electron-builder build its own
|
|
# (broken) keychain. Point it at the one imported above instead.
|
|
CSC_KEYCHAIN: ${{ runner.temp }}/ever-signing.keychain-db
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
APPLE_API_KEY: /tmp/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
|
|
- name: Scrub registry credentials
|
|
if: always()
|
|
shell: bash
|
|
# DELIBERATELY INLINE, not a composite action. A local action is resolved from the
|
|
# workspace, so a failed checkout means it cannot load and this step errors instead of
|
|
# running. 30 of these jobs check out with `clean: false`, where the previous run's
|
|
# workspace - and any live _authToken in it - survives; that is the exact case this step
|
|
# exists to cover. Configure Registry is a composite action because it genuinely needs the checkout.
|
|
run: |
|
|
# The auth token must not outlive the job. These runners check out with clean: false and
|
|
# clean only dist/ and node_modules/, so a workspace .npmrc carrying
|
|
# //packages.ever.co/:_authToken=... would sit on disk after the job ends - readable by
|
|
# anything scheduled on this runner before the next Configure Registry step resets it.
|
|
#
|
|
# This is deliberately the LAST step of the job: the build steps above run
|
|
# postinstall.electron / electron-builder install-app-deps, which resolve dependencies,
|
|
# so the credential has to survive until they are done. Only the credential lines go;
|
|
# the registry= line stays. Runs on failure too, which is when it would linger.
|
|
#
|
|
# No 'sed -i.bak': the backup would itself hold the token if this step were interrupted.
|
|
# The temp file only ever holds the SCRUBBED content, so a partial run leaks nothing.
|
|
# Cleanup policy, precisely:
|
|
# * individual cleanup ATTEMPTS are best-effort. Under `set -e` a failing sed or mv
|
|
# would abort this step before the token was removed - the exact outcome the step
|
|
# exists to prevent - so nothing is allowed to short-circuit it.
|
|
# * the POSTCONDITION is not best-effort. A surviving CREDENTIAL fails the step, because
|
|
# handing a live token to the next job on a reused clean: false runner is worse than a
|
|
# red build. Leftover registry STATE (a stale .yarnrc, a yarn.lock still rewritten to
|
|
# the VIP) only warns: it is a correctness nuisance for an unrelated workflow, not a
|
|
# secret, and the next Configure Registry step resets it anyway.
|
|
# Anything that cannot be DETERMINED counts as dirty, so an unreadable file is never
|
|
# mistaken for a clean one (grep exits 2 on a read error, which is not "no token").
|
|
set +e
|
|
|
|
# Restoring the tracked files from git is the primary mechanism: it reverts the whole
|
|
# file, so the credential, the appended registry= line and the yarn.lock rewrite all go
|
|
# in one operation.
|
|
git checkout -- .npmrc yarn.lock 2>/dev/null
|
|
# Fallback for a workspace where git cannot run at all.
|
|
if [ -f .npmrc ]; then
|
|
sed -e '/_authToken=/d' -e '/always-auth=/d' .npmrc > .npmrc.scrubbed 2>/dev/null && mv -f .npmrc.scrubbed .npmrc
|
|
fi
|
|
rm -f .npmrc.bak .npmrc.scrubbed .yarnrc yarn.lock.bak yarn.lock.rewritten
|
|
|
|
# Prove the credential is gone, starting from "undetermined" rather than "absent" so no
|
|
# inconclusive result can pass. Two ways to be inconclusive: grep exits 2 when a file
|
|
# cannot be READ, and [ -f ] answers false for both "missing" and "cannot stat", so a
|
|
# bare existence test cannot tell an absent file from an unreachable one.
|
|
cred_state="undetermined"
|
|
if [ -e .npmrc ] || [ -L .npmrc ]; then
|
|
grep -q '_authToken=' .npmrc
|
|
case "$?" in
|
|
0) cred_state="present" ;;
|
|
1) cred_state="absent" ;;
|
|
*) cred_state="undetermined" ;;
|
|
esac
|
|
elif [ -r . ] && [ -x . ]; then
|
|
# The directory is both readable AND searchable and neither a file nor a symlink named
|
|
# .npmrc exists, so the absence is proven rather than merely unobservable. Without the
|
|
# -x test a stat could fail in a directory that still answers -r, and without the -L
|
|
# test above a dangling symlink would read as "missing" while its target held a token.
|
|
cred_state="absent"
|
|
fi
|
|
# Artifacts that can also carry the token: .npmrc.bak is written by older revisions of
|
|
# this workflow, and a surviving .npmrc.scrubbed means the mv above did not complete.
|
|
# Present-but-clean is only clutter; present-and-carrying-a-token (or unreadable) is not.
|
|
for stray in .npmrc.bak .npmrc.scrubbed; do
|
|
if [ -e "$stray" ] || [ -L "$stray" ]; then
|
|
grep -q '_authToken=' "$stray"
|
|
if [ "$?" -ne 1 ]; then
|
|
cred_state="present in $stray"
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$cred_state" != "absent" ]; then
|
|
echo "::error title=Registry credential may still be present::Auth token is $cred_state after cleanup on ${RUNNER_NAME:-this runner}."
|
|
exit 1
|
|
fi
|
|
|
|
# Report - but do not fail on - leftover registry state.
|
|
leftover=""
|
|
[ -e .yarnrc ] && leftover="$leftover .yarnrc"
|
|
git diff --quiet -- .npmrc yarn.lock 2>/dev/null
|
|
case "$?" in
|
|
0) ;;
|
|
1) leftover="$leftover .npmrc/yarn.lock(modified)" ;;
|
|
*) leftover="$leftover .npmrc/yarn.lock(unverifiable)" ;;
|
|
esac
|
|
if [ -n "$leftover" ]; then
|
|
echo "::warning title=Registry state left behind::Cleanup could not fully restore:$leftover on ${RUNNER_NAME:-this runner}. The next Configure Registry step resets it, but a job from another workflow could inherit it first."
|
|
else
|
|
echo "Registry credential removed; .npmrc, .yarnrc and yarn.lock restored to HEAD."
|
|
fi
|
|
release-windows:
|
|
needs: check-release-tag
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 300
|
|
|
|
strategy:
|
|
matrix:
|
|
os: [[self-hosted, Windows, X64]]
|
|
|
|
steps:
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@v5
|
|
with:
|
|
clean: false
|
|
|
|
- name: Selective cleanup (preserve .nx/cache)
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
# Stop NX daemon first to release file locks before cleanup (guard for fresh runners without Node)
|
|
if (Get-Command npx -ErrorAction SilentlyContinue) { npx nx daemon --stop 2>&1 | Out-Null }
|
|
# Remove build artifacts but keep NX cache for faster rebuilds
|
|
if (Test-Path "dist") { Remove-Item -Recurse -Force "dist" }
|
|
if (Test-Path "node_modules") { Remove-Item -Recurse -Force "node_modules" }
|
|
exit 0
|
|
|
|
- name: Install Node.js, NPM and Yarn
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24.17.0
|
|
|
|
- name: Install Visual Studio 2022 Build Tools (VCTools)
|
|
shell: powershell
|
|
run: |
|
|
choco install -y visualstudio2022buildtools --execution-timeout=21600 --package-parameters "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --includeOptional --passive --norestart"
|
|
|
|
- name: Configure node-gyp to use VS 2022
|
|
shell: powershell
|
|
run: |
|
|
"GYP_MSVS_VERSION=2022" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
"npm_config_msvs_version=2022" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
|
|
- name: Fix node-gyp and Python
|
|
run: python3 -m pip install packaging setuptools
|
|
|
|
- name: Setup MSVC (VS 2022 dev env)
|
|
uses: ilammy/msvc-dev-cmd@v1
|
|
with:
|
|
arch: x64
|
|
|
|
- name: Install latest version of NPM
|
|
run: 'npm install -g npm@11.6.2'
|
|
|
|
- name: Install globally node-gyp, ts-node and nx packages
|
|
run: 'npm install --quiet -g node-gyp@10.2.0 ts-node@10.9.2 nx@^22.5.2'
|
|
|
|
- name: Configure npm python for node-gyp
|
|
shell: powershell
|
|
run: |
|
|
$py = (Get-Command python.exe).Source
|
|
Write-Host "python is: $py"
|
|
"npm_config_python=$py" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
"PYTHON=$py" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
|
|
- name: Configure Registry
|
|
uses: ./.github/actions/configure-registry
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# in-network runners: the self-hosted Windows boxes, plus any ever-k8s-* ARC pool a runner
|
|
# variable may select (release-linux takes its os from vars.RUNNER_LINUX_APPS_X64). Keep the
|
|
# ever-k8s disjunct even where a matrix cannot currently emit that label - it costs nothing,
|
|
# keeps all 66 call sites identical, and means a future ARC matrix entry inherits the VIP
|
|
# retry and the in-network warning instead of silently losing them.
|
|
expect-vip: ${{ contains(matrix.os, 'self-hosted') || contains(matrix.os, 'ever-k8s') }}
|
|
|
|
- name: Install Yarn dependencies
|
|
run: 'yarn install --network-timeout 1000000 --frozen-lockfile --ignore-scripts'
|
|
|
|
- name: Run Postinstall Manually
|
|
run: 'yarn postinstall.manual'
|
|
|
|
- name: Bump Server version
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const script = require('./.scripts/bump-version-electron.js')
|
|
console.log(script.serverapi(true))
|
|
env:
|
|
# Windows signing config must be visible to the BUMP step: this script writes build.win.azureSignOptions into package.json, which electron-builder reads later.
|
|
WINDOWS_PUBLISHER_NAME: ${{ secrets.WINDOWS_PUBLISHER_NAME }}
|
|
AZURE_CERT_PROFILE_NAME: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
|
|
AZURE_CODE_SIGNING_ACCOUNT: ${{ vars.AZURE_CODE_SIGNING_ACCOUNT || 'ever' }}
|
|
AZURE_CODE_SIGNING_ENDPOINT: ${{ vars.AZURE_CODE_SIGNING_ENDPOINT || 'https://eus.codesigning.azure.net/' }}
|
|
GAUZY_RELEASE_TAG: ${{ needs.check-release-tag.outputs.tag }}
|
|
PROJECT_REPO: 'https://github.com/ever-co/ever-gauzy.git'
|
|
DESKTOP_API_SERVER_APP_NAME: 'gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_NAME: 'ever-gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_OWNER: 'ever-co'
|
|
COMPANY_SITE_LINK: 'https://gauzy.co'
|
|
DESKTOP_API_SERVER_APP_DESCRIPTION: 'Gauzy API Server'
|
|
DESKTOP_API_SERVER_APP_ID: 'com.ever.gauzyapiserver'
|
|
|
|
- name: Fix Node.js PATH for child processes
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$nodeExe = (Get-Command node -ErrorAction Stop).Source
|
|
$nodePath = Split-Path $nodeExe -Parent
|
|
$npmGlobalBin = & npm config get prefix
|
|
$localBin = Join-Path $PWD "node_modules\.bin"
|
|
$yarnCmd = Get-Command yarn -ErrorAction SilentlyContinue
|
|
$yarnPath = if ($yarnCmd) { Split-Path $yarnCmd.Source -Parent } else { "" }
|
|
|
|
$npmNodeExe = Join-Path $npmGlobalBin "node.exe"
|
|
if (-not (Test-Path $npmNodeExe)) { Copy-Item $nodeExe $npmNodeExe -Force }
|
|
|
|
$localNodeExe = Join-Path $localBin "node.exe"
|
|
if (-not (Test-Path $localNodeExe)) { Copy-Item $nodeExe $localNodeExe -Force }
|
|
|
|
$newPath = "$nodePath;$npmGlobalBin;$localBin;$yarnPath;$($env:PATH)"
|
|
"PATH=$newPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
|
|
@($nodePath, $npmGlobalBin, $localBin, $yarnPath) | Where-Object { $_ } | ForEach-Object {
|
|
$_ | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
|
|
}
|
|
|
|
"NODE=$nodeExe" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
"NODE_PATH=$nodePath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
|
|
$env:PATH = $newPath
|
|
[System.Environment]::SetEnvironmentVariable("PATH", $newPath, "Process")
|
|
|
|
- name: Ensure dist directory exists
|
|
shell: bash
|
|
run: mkdir -p dist/packages
|
|
|
|
- name: Increase file handle limits
|
|
shell: powershell
|
|
run: |
|
|
# Increase Node.js UV threadpool for parallel I/O (default is 4)
|
|
"UV_THREADPOOL_SIZE=32" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
# Patch graceful-fs to retry EMFILE errors with backoff
|
|
node -e "try { var gfs = require('graceful-fs'); gfs.gracefulify(require('fs')); console.log('graceful-fs patched'); } catch(e) { console.log('graceful-fs not available, skipping'); }"
|
|
|
|
- name: Reset NX
|
|
shell: powershell
|
|
run: npx nx reset
|
|
|
|
# Azure Trusted Signing runs Invoke-TrustedSigning, which installs the `sign` dotnet
|
|
# global tool. The self-hosted Windows runners have no .NET SDK, so that install fails
|
|
# ("sdk-not-found") and signing is skipped. Provision it here rather than on the host,
|
|
# so the requirement lives in Git and applies to every runner.
|
|
- name: Install .NET SDK (required by Azure Trusted Signing)
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '8.0.x'
|
|
|
|
- name: Build Server
|
|
shell: cmd
|
|
run: 'yarn build:gauzy-api-server:windows:release:gh:x64'
|
|
env:
|
|
USE_HARD_LINKS: false
|
|
ELECTRON_BUILDER_CACHE: ${{ github.workspace }}\.cache\electron-builder
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
# Windows Authenticode signing (electron-builder auto-signs when WIN_CSC_LINK is set;
|
|
# empty secret => skipped). Verification engages only when WINDOWS_PUBLISHER_NAME is set.
|
|
WIN_CSC_LINK: ${{ secrets.WINDOWS_CERT_PFX_BASE64 }}
|
|
WIN_CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }}
|
|
WINDOWS_PUBLISHER_NAME: ${{ secrets.WINDOWS_PUBLISHER_NAME }}
|
|
# Azure Artifact Signing (preferred once a certificate profile exists). Engaged only when
|
|
# AZURE_CERT_PROFILE_NAME is set; otherwise the PFX path above is used.
|
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
|
AZURE_CERT_PROFILE_NAME: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
|
|
AZURE_CODE_SIGNING_ACCOUNT: ${{ vars.AZURE_CODE_SIGNING_ACCOUNT || 'ever' }}
|
|
AZURE_CODE_SIGNING_ENDPOINT: ${{ vars.AZURE_CODE_SIGNING_ENDPOINT || 'https://eus.codesigning.azure.net/' }}
|
|
EP_GH_IGNORE_TIME: true
|
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
|
|
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
|
|
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
|
|
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
|
|
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
|
|
NX_NO_CLOUD: true
|
|
NX_PLUGIN_NO_TIMEOUTS: true
|
|
NX_DAEMON: false
|
|
|
|
- name: Scrub registry credentials
|
|
if: always()
|
|
shell: bash
|
|
# DELIBERATELY INLINE, not a composite action. A local action is resolved from the
|
|
# workspace, so a failed checkout means it cannot load and this step errors instead of
|
|
# running. 30 of these jobs check out with `clean: false`, where the previous run's
|
|
# workspace - and any live _authToken in it - survives; that is the exact case this step
|
|
# exists to cover. Configure Registry is a composite action because it genuinely needs the checkout.
|
|
run: |
|
|
# The auth token must not outlive the job. These runners check out with clean: false and
|
|
# clean only dist/ and node_modules/, so a workspace .npmrc carrying
|
|
# //packages.ever.co/:_authToken=... would sit on disk after the job ends - readable by
|
|
# anything scheduled on this runner before the next Configure Registry step resets it.
|
|
#
|
|
# This is deliberately the LAST step of the job: the build steps above run
|
|
# postinstall.electron / electron-builder install-app-deps, which resolve dependencies,
|
|
# so the credential has to survive until they are done. Only the credential lines go;
|
|
# the registry= line stays. Runs on failure too, which is when it would linger.
|
|
#
|
|
# No 'sed -i.bak': the backup would itself hold the token if this step were interrupted.
|
|
# The temp file only ever holds the SCRUBBED content, so a partial run leaks nothing.
|
|
# Cleanup policy, precisely:
|
|
# * individual cleanup ATTEMPTS are best-effort. Under `set -e` a failing sed or mv
|
|
# would abort this step before the token was removed - the exact outcome the step
|
|
# exists to prevent - so nothing is allowed to short-circuit it.
|
|
# * the POSTCONDITION is not best-effort. A surviving CREDENTIAL fails the step, because
|
|
# handing a live token to the next job on a reused clean: false runner is worse than a
|
|
# red build. Leftover registry STATE (a stale .yarnrc, a yarn.lock still rewritten to
|
|
# the VIP) only warns: it is a correctness nuisance for an unrelated workflow, not a
|
|
# secret, and the next Configure Registry step resets it anyway.
|
|
# Anything that cannot be DETERMINED counts as dirty, so an unreadable file is never
|
|
# mistaken for a clean one (grep exits 2 on a read error, which is not "no token").
|
|
set +e
|
|
|
|
# Restoring the tracked files from git is the primary mechanism: it reverts the whole
|
|
# file, so the credential, the appended registry= line and the yarn.lock rewrite all go
|
|
# in one operation.
|
|
git checkout -- .npmrc yarn.lock 2>/dev/null
|
|
# Fallback for a workspace where git cannot run at all.
|
|
if [ -f .npmrc ]; then
|
|
sed -e '/_authToken=/d' -e '/always-auth=/d' .npmrc > .npmrc.scrubbed 2>/dev/null && mv -f .npmrc.scrubbed .npmrc
|
|
fi
|
|
rm -f .npmrc.bak .npmrc.scrubbed .yarnrc yarn.lock.bak yarn.lock.rewritten
|
|
|
|
# Prove the credential is gone, starting from "undetermined" rather than "absent" so no
|
|
# inconclusive result can pass. Two ways to be inconclusive: grep exits 2 when a file
|
|
# cannot be READ, and [ -f ] answers false for both "missing" and "cannot stat", so a
|
|
# bare existence test cannot tell an absent file from an unreachable one.
|
|
cred_state="undetermined"
|
|
if [ -e .npmrc ] || [ -L .npmrc ]; then
|
|
grep -q '_authToken=' .npmrc
|
|
case "$?" in
|
|
0) cred_state="present" ;;
|
|
1) cred_state="absent" ;;
|
|
*) cred_state="undetermined" ;;
|
|
esac
|
|
elif [ -r . ] && [ -x . ]; then
|
|
# The directory is both readable AND searchable and neither a file nor a symlink named
|
|
# .npmrc exists, so the absence is proven rather than merely unobservable. Without the
|
|
# -x test a stat could fail in a directory that still answers -r, and without the -L
|
|
# test above a dangling symlink would read as "missing" while its target held a token.
|
|
cred_state="absent"
|
|
fi
|
|
# Artifacts that can also carry the token: .npmrc.bak is written by older revisions of
|
|
# this workflow, and a surviving .npmrc.scrubbed means the mv above did not complete.
|
|
# Present-but-clean is only clutter; present-and-carrying-a-token (or unreadable) is not.
|
|
for stray in .npmrc.bak .npmrc.scrubbed; do
|
|
if [ -e "$stray" ] || [ -L "$stray" ]; then
|
|
grep -q '_authToken=' "$stray"
|
|
if [ "$?" -ne 1 ]; then
|
|
cred_state="present in $stray"
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$cred_state" != "absent" ]; then
|
|
echo "::error title=Registry credential may still be present::Auth token is $cred_state after cleanup on ${RUNNER_NAME:-this runner}."
|
|
exit 1
|
|
fi
|
|
|
|
# Report - but do not fail on - leftover registry state.
|
|
leftover=""
|
|
[ -e .yarnrc ] && leftover="$leftover .yarnrc"
|
|
git diff --quiet -- .npmrc yarn.lock 2>/dev/null
|
|
case "$?" in
|
|
0) ;;
|
|
1) leftover="$leftover .npmrc/yarn.lock(modified)" ;;
|
|
*) leftover="$leftover .npmrc/yarn.lock(unverifiable)" ;;
|
|
esac
|
|
if [ -n "$leftover" ]; then
|
|
echo "::warning title=Registry state left behind::Cleanup could not fully restore:$leftover on ${RUNNER_NAME:-this runner}. The next Configure Registry step resets it, but a job from another workflow could inherit it first."
|
|
else
|
|
echo "Registry credential removed; .npmrc, .yarnrc and yarn.lock restored to HEAD."
|
|
fi
|
|
release-windows-arm64:
|
|
needs: check-release-tag
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 300
|
|
|
|
strategy:
|
|
matrix:
|
|
os: [windows-11-arm]
|
|
|
|
steps:
|
|
- name: Check out Git repository
|
|
uses: actions/checkout@v5
|
|
with:
|
|
clean: false
|
|
|
|
- name: Selective cleanup (preserve .nx/cache)
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
# Stop NX daemon first to release file locks before cleanup (guard for fresh runners without Node)
|
|
if (Get-Command npx -ErrorAction SilentlyContinue) { npx nx daemon --stop 2>&1 | Out-Null }
|
|
# Remove build artifacts but keep NX cache for faster rebuilds
|
|
if (Test-Path "dist") { Remove-Item -Recurse -Force "dist" }
|
|
if (Test-Path "node_modules") { Remove-Item -Recurse -Force "node_modules" }
|
|
exit 0
|
|
|
|
- name: Install Node.js, NPM and Yarn
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24.17.0
|
|
architecture: arm64
|
|
|
|
- name: Get yarn cache directory path
|
|
id: yarn-cache-dir-path
|
|
shell: bash
|
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
|
|
- uses: actions/cache@v5
|
|
id: yarn-cache
|
|
with:
|
|
path: |
|
|
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
.nx/cache
|
|
key: ${{ runner.os }}-${{ runner.arch }}-yarn-nx-${{ hashFiles('yarn.lock') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-nx-
|
|
${{ runner.os }}-${{ runner.arch }}-yarn-
|
|
|
|
- name: Install Visual Studio 2022 Build Tools (VCTools with ARM64)
|
|
shell: powershell
|
|
run: |
|
|
# The runner image normally ships VS with the VC ARM64 toolset. Only reach for
|
|
# Chocolatey if it is genuinely missing — community.chocolatey.org returning 504
|
|
# has failed this job before ("Chocolatey installed 0/0 packages"), and installing
|
|
# something already present costs ~6 min for nothing.
|
|
$ErrorActionPreference = "Continue"
|
|
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
$have = $false
|
|
if (Test-Path $vswhere) {
|
|
$found = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.ARM64 -property installationPath 2>$null
|
|
if ($found) { $have = $true; Write-Host "VC ARM64 toolset already present: $found" }
|
|
}
|
|
if (-not $have) {
|
|
Write-Host "VC ARM64 toolset not found - installing via Chocolatey"
|
|
choco install -y visualstudio2022buildtools --execution-timeout=21600 --package-parameters "--add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 --includeRecommended --passive --norestart"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
# Do not fail the job on a Chocolatey feed outage; the next step (msvc-dev-cmd)
|
|
# will either find a usable toolchain or fail with an unambiguous message.
|
|
Write-Warning "Chocolatey install failed (exit $LASTEXITCODE) - continuing; the MSVC setup step will report definitively."
|
|
}
|
|
}
|
|
exit 0
|
|
|
|
- name: Configure node-gyp to use VS 2022
|
|
shell: powershell
|
|
run: |
|
|
"GYP_MSVS_VERSION=2022" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
"npm_config_msvs_version=2022" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
|
|
- name: Fix node-gyp and Python
|
|
run: python3 -m pip install packaging setuptools
|
|
|
|
- name: Setup MSVC (VS 2022 dev env)
|
|
uses: ilammy/msvc-dev-cmd@v1
|
|
with:
|
|
arch: arm64
|
|
|
|
- name: Install latest version of NPM
|
|
run: 'npm install -g npm@11.6.2'
|
|
|
|
- name: Install globally node-gyp, ts-node and nx packages
|
|
run: 'npm install --quiet -g node-gyp@10.2.0 ts-node@10.9.2 nx@^22.5.2'
|
|
|
|
- name: Configure npm python for node-gyp
|
|
shell: powershell
|
|
run: |
|
|
$py = (Get-Command python.exe).Source
|
|
Write-Host "python is: $py"
|
|
"npm_config_python=$py" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
"PYTHON=$py" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
|
|
- name: Configure Registry
|
|
uses: ./.github/actions/configure-registry
|
|
with:
|
|
verdaccio-registry: ${{ vars.VERDACCIO_REGISTRY }}
|
|
verdaccio-token: ${{ secrets.VERDACCIO_TOKEN }}
|
|
force-public: ${{ vars.VERDACCIO_FORCE_PUBLIC }}
|
|
# in-network runners: the self-hosted Windows boxes, plus any ever-k8s-* ARC pool a runner
|
|
# variable may select (release-linux takes its os from vars.RUNNER_LINUX_APPS_X64). Keep the
|
|
# ever-k8s disjunct even where a matrix cannot currently emit that label - it costs nothing,
|
|
# keeps all 66 call sites identical, and means a future ARC matrix entry inherits the VIP
|
|
# retry and the in-network warning instead of silently losing them.
|
|
expect-vip: ${{ contains(matrix.os, 'self-hosted') || contains(matrix.os, 'ever-k8s') }}
|
|
|
|
- name: Install Yarn dependencies
|
|
run: 'yarn install --network-timeout 1000000 --frozen-lockfile --ignore-scripts'
|
|
|
|
- name: Run Postinstall Manually
|
|
run: 'yarn postinstall.manual'
|
|
|
|
- name: Bump version server api app
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const script = require('./.scripts/bump-version-electron.js')
|
|
console.log(script.serverapi(true))
|
|
env:
|
|
# Windows signing is deliberately NOT configured for ARM64. Azure Trusted Signing
|
|
# ships no ARM64 tooling (Microsoft.Trusted.Signing.Client 1.0.95 contains only
|
|
# bin/x64 and bin/x86), so Invoke-TrustedSigning loads the x64 dlib and fails with
|
|
# "SignTool failed with exit code 3", taking the whole build down. Without
|
|
# WINDOWS_PUBLISHER_NAME the bump script emits no signer, so ARM64 builds
|
|
# unsigned and succeeds; electron-updater verification stays off for it. x64 signs.
|
|
GAUZY_RELEASE_TAG: ${{ needs.check-release-tag.outputs.tag }}
|
|
PROJECT_REPO: 'https://github.com/ever-co/ever-gauzy.git'
|
|
DESKTOP_API_SERVER_APP_NAME: 'gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_NAME: 'ever-gauzy-api-server'
|
|
DESKTOP_API_SERVER_REPO_OWNER: 'ever-co'
|
|
COMPANY_SITE_LINK: 'https://gauzy.co'
|
|
DESKTOP_API_SERVER_APP_DESCRIPTION: 'Gauzy API Server'
|
|
DESKTOP_API_SERVER_APP_ID: 'com.ever.gauzyapiserver'
|
|
|
|
- name: Fix Node.js PATH for child processes
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$nodeExe = (Get-Command node -ErrorAction Stop).Source
|
|
$nodePath = Split-Path $nodeExe -Parent
|
|
$npmGlobalBin = & npm config get prefix
|
|
$localBin = Join-Path $PWD "node_modules\.bin"
|
|
$yarnCmd = Get-Command yarn -ErrorAction SilentlyContinue
|
|
$yarnPath = if ($yarnCmd) { Split-Path $yarnCmd.Source -Parent } else { "" }
|
|
|
|
$npmNodeExe = Join-Path $npmGlobalBin "node.exe"
|
|
if (-not (Test-Path $npmNodeExe)) { Copy-Item $nodeExe $npmNodeExe -Force }
|
|
|
|
$localNodeExe = Join-Path $localBin "node.exe"
|
|
if (-not (Test-Path $localNodeExe)) { Copy-Item $nodeExe $localNodeExe -Force }
|
|
|
|
$newPath = "$nodePath;$npmGlobalBin;$localBin;$yarnPath;$($env:PATH)"
|
|
"PATH=$newPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
|
|
@($nodePath, $npmGlobalBin, $localBin, $yarnPath) | Where-Object { $_ } | ForEach-Object {
|
|
$_ | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
|
|
}
|
|
|
|
"NODE=$nodeExe" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
"NODE_PATH=$nodePath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
|
|
|
$env:PATH = $newPath
|
|
[System.Environment]::SetEnvironmentVariable("PATH", $newPath, "Process")
|
|
|
|
- name: Ensure dist directory exists
|
|
shell: bash
|
|
run: mkdir -p dist/packages
|
|
|
|
- name: Increase file handle limits
|
|
shell: powershell
|
|
run: |
|
|
# Increase Node.js UV threadpool for parallel I/O (default is 4)
|
|
"UV_THREADPOOL_SIZE=32" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
# Patch graceful-fs to retry EMFILE errors with backoff
|
|
node -e "try { var gfs = require('graceful-fs'); gfs.gracefulify(require('fs')); console.log('graceful-fs patched'); } catch(e) { console.log('graceful-fs not available, skipping'); }"
|
|
|
|
# Azure Trusted Signing runs Invoke-TrustedSigning, which installs the `sign` dotnet
|
|
# global tool. The self-hosted Windows runners have no .NET SDK, so that install fails
|
|
# ("sdk-not-found") and signing is skipped. Provision it here rather than on the host,
|
|
# so the requirement lives in Git and applies to every runner.
|
|
- name: Install .NET SDK (required by Azure Trusted Signing)
|
|
uses: actions/setup-dotnet@v4
|
|
with:
|
|
dotnet-version: '8.0.x'
|
|
|
|
- name: Build Server API
|
|
shell: cmd
|
|
run: yarn build:gauzy-api-server:windows:release:gh:arm64
|
|
env:
|
|
USE_HARD_LINKS: false
|
|
ELECTRON_BUILDER_CACHE: ${{ github.workspace }}\.cache\electron-builder
|
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
# WIN_CSC_LINK is deliberately NOT passed on ARM64. electron-builder auto-signs
|
|
# whenever it is set, independently of azureSignOptions, and then spawns a signtool
|
|
# that does not exist for ARM64 in its bundled winCodeSign package:
|
|
# spawn ...\winCodeSign-2.6.0\windows-10\arm64\signtool.exe ENOENT
|
|
# ARM64 therefore ships unsigned (see the Bump step). x64 still signs via Azure.
|
|
WINDOWS_PUBLISHER_NAME: ${{ secrets.WINDOWS_PUBLISHER_NAME }}
|
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
|
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
|
AZURE_CERT_PROFILE_NAME: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
|
|
AZURE_CODE_SIGNING_ACCOUNT: ${{ vars.AZURE_CODE_SIGNING_ACCOUNT || 'ever' }}
|
|
AZURE_CODE_SIGNING_ENDPOINT: ${{ vars.AZURE_CODE_SIGNING_ENDPOINT || 'https://eus.codesigning.azure.net/' }}
|
|
EP_GH_IGNORE_TIME: true
|
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}'
|
|
SENTRY_PROFILE_SAMPLE_RATE: '${{ secrets.SENTRY_PROFILE_SAMPLE_RATE }}'
|
|
SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}'
|
|
SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}'
|
|
SENTRY_PROFILING_ENABLED: '${{ secrets.SENTRY_PROFILING_ENABLED }}'
|
|
NX_NO_CLOUD: true
|
|
NX_PLUGIN_NO_TIMEOUTS: true
|
|
NX_DAEMON: false
|
|
|
|
- name: Scrub registry credentials
|
|
if: always()
|
|
shell: bash
|
|
# DELIBERATELY INLINE, not a composite action. A local action is resolved from the
|
|
# workspace, so a failed checkout means it cannot load and this step errors instead of
|
|
# running. 30 of these jobs check out with `clean: false`, where the previous run's
|
|
# workspace - and any live _authToken in it - survives; that is the exact case this step
|
|
# exists to cover. Configure Registry is a composite action because it genuinely needs the checkout.
|
|
run: |
|
|
# The auth token must not outlive the job. These runners check out with clean: false and
|
|
# clean only dist/ and node_modules/, so a workspace .npmrc carrying
|
|
# //packages.ever.co/:_authToken=... would sit on disk after the job ends - readable by
|
|
# anything scheduled on this runner before the next Configure Registry step resets it.
|
|
#
|
|
# This is deliberately the LAST step of the job: the build steps above run
|
|
# postinstall.electron / electron-builder install-app-deps, which resolve dependencies,
|
|
# so the credential has to survive until they are done. Only the credential lines go;
|
|
# the registry= line stays. Runs on failure too, which is when it would linger.
|
|
#
|
|
# No 'sed -i.bak': the backup would itself hold the token if this step were interrupted.
|
|
# The temp file only ever holds the SCRUBBED content, so a partial run leaks nothing.
|
|
# Cleanup policy, precisely:
|
|
# * individual cleanup ATTEMPTS are best-effort. Under `set -e` a failing sed or mv
|
|
# would abort this step before the token was removed - the exact outcome the step
|
|
# exists to prevent - so nothing is allowed to short-circuit it.
|
|
# * the POSTCONDITION is not best-effort. A surviving CREDENTIAL fails the step, because
|
|
# handing a live token to the next job on a reused clean: false runner is worse than a
|
|
# red build. Leftover registry STATE (a stale .yarnrc, a yarn.lock still rewritten to
|
|
# the VIP) only warns: it is a correctness nuisance for an unrelated workflow, not a
|
|
# secret, and the next Configure Registry step resets it anyway.
|
|
# Anything that cannot be DETERMINED counts as dirty, so an unreadable file is never
|
|
# mistaken for a clean one (grep exits 2 on a read error, which is not "no token").
|
|
set +e
|
|
|
|
# Restoring the tracked files from git is the primary mechanism: it reverts the whole
|
|
# file, so the credential, the appended registry= line and the yarn.lock rewrite all go
|
|
# in one operation.
|
|
git checkout -- .npmrc yarn.lock 2>/dev/null
|
|
# Fallback for a workspace where git cannot run at all.
|
|
if [ -f .npmrc ]; then
|
|
sed -e '/_authToken=/d' -e '/always-auth=/d' .npmrc > .npmrc.scrubbed 2>/dev/null && mv -f .npmrc.scrubbed .npmrc
|
|
fi
|
|
rm -f .npmrc.bak .npmrc.scrubbed .yarnrc yarn.lock.bak yarn.lock.rewritten
|
|
|
|
# Prove the credential is gone, starting from "undetermined" rather than "absent" so no
|
|
# inconclusive result can pass. Two ways to be inconclusive: grep exits 2 when a file
|
|
# cannot be READ, and [ -f ] answers false for both "missing" and "cannot stat", so a
|
|
# bare existence test cannot tell an absent file from an unreachable one.
|
|
cred_state="undetermined"
|
|
if [ -e .npmrc ] || [ -L .npmrc ]; then
|
|
grep -q '_authToken=' .npmrc
|
|
case "$?" in
|
|
0) cred_state="present" ;;
|
|
1) cred_state="absent" ;;
|
|
*) cred_state="undetermined" ;;
|
|
esac
|
|
elif [ -r . ] && [ -x . ]; then
|
|
# The directory is both readable AND searchable and neither a file nor a symlink named
|
|
# .npmrc exists, so the absence is proven rather than merely unobservable. Without the
|
|
# -x test a stat could fail in a directory that still answers -r, and without the -L
|
|
# test above a dangling symlink would read as "missing" while its target held a token.
|
|
cred_state="absent"
|
|
fi
|
|
# Artifacts that can also carry the token: .npmrc.bak is written by older revisions of
|
|
# this workflow, and a surviving .npmrc.scrubbed means the mv above did not complete.
|
|
# Present-but-clean is only clutter; present-and-carrying-a-token (or unreadable) is not.
|
|
for stray in .npmrc.bak .npmrc.scrubbed; do
|
|
if [ -e "$stray" ] || [ -L "$stray" ]; then
|
|
grep -q '_authToken=' "$stray"
|
|
if [ "$?" -ne 1 ]; then
|
|
cred_state="present in $stray"
|
|
fi
|
|
fi
|
|
done
|
|
if [ "$cred_state" != "absent" ]; then
|
|
echo "::error title=Registry credential may still be present::Auth token is $cred_state after cleanup on ${RUNNER_NAME:-this runner}."
|
|
exit 1
|
|
fi
|
|
|
|
# Report - but do not fail on - leftover registry state.
|
|
leftover=""
|
|
[ -e .yarnrc ] && leftover="$leftover .yarnrc"
|
|
git diff --quiet -- .npmrc yarn.lock 2>/dev/null
|
|
case "$?" in
|
|
0) ;;
|
|
1) leftover="$leftover .npmrc/yarn.lock(modified)" ;;
|
|
*) leftover="$leftover .npmrc/yarn.lock(unverifiable)" ;;
|
|
esac
|
|
if [ -n "$leftover" ]; then
|
|
echo "::warning title=Registry state left behind::Cleanup could not fully restore:$leftover on ${RUNNER_NAME:-this runner}. The next Configure Registry step resets it, but a job from another workflow could inherit it first."
|
|
else
|
|
echo "Registry credential removed; .npmrc, .yarnrc and yarn.lock restored to HEAD."
|
|
fi
|