Skip to content

feat(emception): shrink npm tarball below 200 MB #948

feat(emception): shrink npm tarball below 200 MB

feat(emception): shrink npm tarball below 200 MB #948

Workflow file for this run

name: Main CI/CD Workflow
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: main-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
detect-changes:
name: Detect changed folders
runs-on: ubuntu-latest
outputs:
api: ${{ steps.filter.outputs.api }}
web: ${{ steps.filter.outputs.web }}
emception: ${{ steps.filter.outputs.emception }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect path changes
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
api:
- 'apps/api/**'
web:
- 'apps/web/**'
emception:
- 'tools/emception/**'
# ── Validation: API build (independent, no npm deps) ───────────────────────
build-api:
name: Build API
needs: detect-changes
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
lfs: false
submodules: true
- name: Setup .NET 9
if: needs.detect-changes.outputs.api == 'true'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Build apps/api
if: needs.detect-changes.outputs.api == 'true'
run: cd apps/api && dotnet build "GameGuild.Production.sln" -c Release
- name: Skip apps/api build (no changes)
if: needs.detect-changes.outputs.api != 'true'
run: echo "No changes in apps/api/**; skipping API build."
# ── Validation: Web build (dotnet-wasm + Next.js) ──────────────────────────
build-web:
name: Build Web
needs: detect-changes
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
lfs: false
submodules: true
- uses: actions/setup-node@v6
if: needs.detect-changes.outputs.web == 'true'
with:
node-version: '22'
package-manager-cache: false
- name: Setup .NET 9
if: needs.detect-changes.outputs.web == 'true'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Install monorepo dependencies
if: needs.detect-changes.outputs.web == 'true'
run: npm install --no-package-lock --include=optional --ignore-scripts
- name: Build packages/dotnet-wasm
if: needs.detect-changes.outputs.web == 'true'
run: npm run setup -w @game-guild/dotnet-wasm
- name: Build apps/web
if: needs.detect-changes.outputs.web == 'true'
run: npm run build -w apps/web
- name: Skip apps/web build (no changes)
if: needs.detect-changes.outputs.web != 'true'
run: echo "No changes in apps/web/**; skipping Web build."
# ── Gource visualization (independent, uploads artifact) ───────────────────
gource:
name: Gource Visualization
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
lfs: false
submodules: true
- name: Install Linux native dependencies
run: |
sudo apt-get update
sudo apt-get install -y --fix-missing ffmpeg gource xvfb
- name: Make gource script executable
run: chmod +x ./contributors/gource.sh
- name: Run headless gource
run: xvfb-run -a ./contributors/gource.sh
- name: Upload gource artifacts
uses: actions/upload-artifact@v4
with:
name: gource
path: |
contributors/gource.mp4
contributors/gource.gif
if-no-files-found: warn
# ── Emception build (critical path, ~30 min on cold cache) ─────────────────
build-emception:
name: Build Emception
needs: detect-changes
runs-on: ubuntu-latest
timeout-minutes: 360
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
lfs: false
submodules: true
- uses: actions/setup-node@v6
with:
node-version: '22'
package-manager-cache: false
- name: Install Linux native dependencies
run: |
sudo apt-get update
sudo apt-get install -y --fix-missing cmake ninja-build build-essential pkg-config brotli
- name: Free disk space and add swap
shell: bash
run: |
sudo rm -rf /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL || true
sudo docker image prune --all --force || true
df -h /
if [[ -z "${ACT:-}" ]]; then
sudo swapoff -a || true
sudo rm -f /mnt/swapfile /swapfile || true
sudo fallocate -l 24G /mnt/swapfile
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile
echo 80 | sudo tee /proc/sys/vm/swappiness
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
else
echo "ℹ️ Skipping swap configuration (act/container environment lacks CAP_SYS_ADMIN)"
fi
free -h
- name: Install monorepo dependencies
run: npm install --no-package-lock --include=optional --ignore-scripts
# Tier 1: Caching ───────────────────────────────────────────────────────
- name: Cache emsdk
uses: actions/cache@v4
id: cache-emsdk
with:
path: tools/emception/tools/emsdk
key: emsdk-${{ runner.os }}-${{ hashFiles('tools/emception/scripts/setup-emsdk.ts') }}
- name: Cache userland (toolchain sources + compiled outputs)
uses: actions/cache@v4
id: cache-userland
with:
path: tools/emception/userland
key: userland-${{ runner.os }}-${{ hashFiles('tools/emception/scripts/build-*.ts', 'tools/emception/scripts/setup-emsdk.ts') }}
restore-keys: |
userland-${{ runner.os }}-
- name: Cache sysroot and CDN bundles
uses: actions/cache@v4
id: cache-cdn
with:
path: |
tools/emception/sysroot
tools/emception/build
tools/emception/public/cdn
key: cdn-${{ runner.os }}-${{ hashFiles('tools/emception/scripts/**') }}
restore-keys: |
cdn-${{ runner.os }}-
- name: Cache emception deploy artifacts
uses: actions/cache@v4
id: cache-emception-artifacts
with:
path: |
tools/emception/apps/ide-react/dist
tools/emception/public/cdn
tools/emception/dist
tools/emception/packages/core/dist
tools/emception/packages/core/cdn
tools/emception/packages/browser/dist
tools/emception/packages/xterm/dist
tools/emception/packages/react/dist
tools/emception/packages/webcomponent/dist
tools/emception/packages/ide/dist
key: emception-artifacts-${{ runner.os }}-${{ hashFiles('tools/emception/package.json', 'tools/emception/tsconfig*.json', 'tools/emception/scripts/**/*.ts', 'tools/emception/packages/**/package.json', 'tools/emception/packages/**/tsconfig*.json', 'tools/emception/apps/ide-react/package.json', 'tools/emception/apps/ide-react/src/**', 'tools/emception/apps/ide-react/vite.config.*') }}
restore-keys: |
emception-artifacts-${{ runner.os }}-
# Build (skipped entirely on full CDN cache hit) ─────────────────────────
- name: Build tools/emception (full)
if: (needs.detect-changes.outputs.emception == 'true' || steps.cache-emception-artifacts.outputs.cache-hit != 'true') && steps.cache-cdn.outputs.cache-hit != 'true'
run: cd tools/emception && npm run build:all
# Always rebuild IDE package (fast TypeScript-only, no emsdk needed) ────
- name: Build IDE package dependencies (core/xterm/browser)
if: (needs.detect-changes.outputs.emception == 'true' || steps.cache-emception-artifacts.outputs.cache-hit != 'true') && steps.cache-cdn.outputs.cache-hit == 'true'
run: |
cd tools/emception
npm run build:packages:core
npm run build:packages:xterm
npm run build:packages:browser
- name: Build @gameguild/emception-ide package
if: needs.detect-changes.outputs.emception == 'true' || steps.cache-emception-artifacts.outputs.cache-hit != 'true'
run: cd tools/emception && npm run build:packages:ide
- name: Sync CDN to demo app
if: needs.detect-changes.outputs.emception == 'true' || steps.cache-emception-artifacts.outputs.cache-hit != 'true'
run: node scripts/sync-emception-cdn.mjs tools/emception/apps/ide-react
- name: Stage CDN into emception package
if: needs.detect-changes.outputs.emception == 'true' || steps.cache-cdn.outputs.cache-hit == 'true' || steps.cache-emception-artifacts.outputs.cache-hit != 'true'
run: cd tools/emception && npm run stage:core:cdn
- name: Build tools/emception/apps/ide-react
if: needs.detect-changes.outputs.emception == 'true' || steps.cache-emception-artifacts.outputs.cache-hit != 'true'
run: cd tools/emception/apps/ide-react && npm run build --ignore-scripts
env:
VITE_BASE: /gameguild/
- name: Upload emception artifacts
uses: actions/upload-artifact@v4
with:
name: emception
path: |
tools/emception/apps/ide-react/dist
tools/emception/public/cdn
tools/emception/dist
tools/emception/packages/core/dist
tools/emception/packages/core/cdn
tools/emception/packages/browser/dist
tools/emception/packages/xterm/dist
tools/emception/packages/react/dist
tools/emception/packages/webcomponent/dist
tools/emception/packages/ide/dist
# ── Deploy: assemble pages + release (needs all jobs above) ────────────────
deploy:
name: Deploy
needs: [build-api, build-web, build-emception, gource]
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pages: write
id-token: write
issues: write
pull-requests: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PAGES_ARTIFACT_NAME: github-pages-${{ github.run_id }}-${{ github.run_attempt }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
lfs: false
submodules: true
- uses: actions/setup-node@v6
with:
node-version: '22'
package-manager-cache: false
- name: Install monorepo dependencies
run: npm install --no-package-lock --include=optional --ignore-scripts
- name: Download emception artifacts
uses: actions/download-artifact@v4
with:
name: emception
path: _artifacts/emception
- name: Restore emception build outputs to workspace
run: |
if [[ -d "_artifacts/emception/tools/emception" ]]; then
echo "Using nested artifact layout"
rsync -a _artifacts/emception/tools/emception/ tools/emception/
else
echo "Using flattened artifact layout"
rsync -a _artifacts/emception/ tools/emception/
fi
- name: Download gource artifacts
uses: actions/download-artifact@v4
with:
name: gource
path: contributors
continue-on-error: true
- name: Assemble GitHub Pages directory
run: |
rm -rf .pages
mkdir -p .pages/contributors
cp -R tools/emception/apps/ide-react/dist/* .pages/
rm -rf .pages/cdn
cp -R tools/emception/public/cdn .pages/cdn
cp -R contributors/* .pages/contributors/
cp contributors/gource.mp4 .pages/contributors/gource.mp4 || echo "::warning::gource.mp4 not found, skipping"
cp contributors/gource.gif .pages/contributors/gource.gif || echo "::warning::gource.gif not found, skipping"
find .pages -name '.gitignore' -delete
echo "Pages directory summary:"
echo " Total size: $(du -sh .pages | cut -f1)"
echo " CDN size: $(du -sh .pages/cdn 2>/dev/null | cut -f1 || echo 'MISSING')"
ls -la .pages/
- name: Upload pages artifact
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
uses: actions/upload-pages-artifact@v3
with:
name: ${{ env.PAGES_ARTIFACT_NAME }}
path: .pages
- name: Deploy to GitHub Pages
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
id: deployment
uses: actions/deploy-pages@v4
with:
artifact_name: ${{ env.PAGES_ARTIFACT_NAME }}
- name: Ensure git safe directory
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
run: git config --global --add safe.directory '*'
- name: Fetch all tags
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
run: |
git fetch --tags --force
echo "Latest tags:"
git tag --sort=-v:refname | head -5
- name: Semantic Release (root)
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
id: semantic
uses: cycjimmy/semantic-release-action@v4
with:
semantic_version: 23
extra_plugins: |
@semantic-release/changelog@6
@semantic-release/git@10
@semantic-release/exec@6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify npm is available
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
run: npm --version
- name: Configure npm registry for npm token publishing
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
uses: actions/setup-node@v6
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- name: Publish emception packages to npm
if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && steps.semantic.outputs.new_release_published == 'true' }}
working-directory: tools/emception
run: |
PACKAGE_DIRS=(
"packages/core"
"packages/xterm"
"packages/browser"
"packages/react"
"packages/webcomponent"
"packages/ide"
)
for DIR in "${PACKAGE_DIRS[@]}"; do
NAME="$(node -p "require('./${DIR}/package.json').name")"
VERSION="$(node -p "require('./${DIR}/package.json').version")"
if npm view "${NAME}@${VERSION}" version >/dev/null 2>&1; then
echo "✅ ${NAME}@${VERSION} already published — skipping"
continue
fi
echo "📦 Publishing ${NAME}@${VERSION}"
npm publish --workspace="${NAME}" --access public
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}