mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
## Summary
The RDE dashboard was bundled into the `@hexclave/cli` npm tarball
(~165MB). That made the `npx @latest` auto-update re-exec pull a huge
tarball, which tripped download firewalls (Socket Firewall
false-positives on Replit → `Lock compromised`).
This decouples the dashboard from the CLI: it's published as a **GitHub
Release artifact** (a zip + `manifest.json` on a floating
`dashboard-latest` release) and fetched at runtime, cached on disk. The
dashboard now rolls forward **independently of the CLI version**
(assuming newest dashboards stay compatible with older CLIs — no compat
gate yet, by design for now).
## How it works
- **Runtime** (`packages/cli/src/lib/dashboard-release.ts`): on
`hexclave dev`, fetch `manifest.json` (`{version, sha256, url}`) from a
stable, no-API GitHub URL → cache under
`<dirname(devEnvStatePath())>/dashboards/<version>/` → streamed
download, **sha256 verify**, extract via the existing `extract-zip` dep,
atomic publish. Offline → fall back to the newest cached build.
- **Wiring** (`dev.ts`): `startDashboardIfNeeded` resolves/launches the
cached release; the restart decision now compares the **dashboard
release** version (not the CLI version). `HEXCLAVE_DASHBOARD_DIR` runs a
local build with zero network; a custom dev dashboard command bypasses
releases entirely.
- **No more npx self-update**: the `npx @latest` re-exec in `hexclave
dev` is removed entirely (along with `self-update.ts`, the
`--no-auto-update` flag, and the binName plumbing that only built the
npx call). It existed to keep the bundled dashboard fresh; now the
dashboard self-updates from GitHub, so the re-exec only added an npx
download on every run — the firewall surface. Users update the CLI via
npm/npx themselves.
- **Publishing** (`scripts/package-dashboard-release.mjs` +
`.github/workflows/dashboard-release.yaml`): build standalone → zip →
write manifest → publish immutable `dashboard-v<version>` release +
clobber the `dashboard-latest` manifest. **Fails loudly** if an existing
tag's asset sha differs from a fresh build (dashboard changed without a
version bump → would otherwise advertise a hash the served zip doesn't
match).
- **Build**: CLI build is now `tsdown`-only (no bundled dashboard);
`turbo.json` drops the dashboard standalone build from the CLI build
graph.
## Testing
- **Unit**: 135 CLI tests pass (manifest parsing incl. path-safe version
validation, URL resolution, version picking, dir override).
- **End-to-end (no GitHub)**: a localhost HTTP server + fixture zip
exercised the full fetch path — download, sha256 verify, cache-hit,
roll-forward, **corrupt-sha rejection**, offline→cache fallback (9/9).
- **Real dashboard, no GitHub**: built the standalone, served the real
76MB zip over `python -m http.server`, and confirmed `hexclave dev`
downloads → verifies → extracts → **boots** the dashboard and registers
an RDE session against the hosted backend. Also validated
`HEXCLAVE_DASHBOARD_DIR` boot.
## Review
Three review agents (runtime correctness, packaging/workflow, code
quality) ran; valid findings fixed: path-traversal hardening on
`version`, a concurrent cache rm/rename race, the workflow
sha-mismatch/atomicity gap, plus `errorMessage` dedup and added tests.
## Notes / follow-ups
- No CLI↔dashboard compatibility gate yet (assumes always-compatible);
easy to add later via a min-CLI field in the manifest. This matters more
now that the CLI no longer auto-updates.
- The publish workflow has not run yet, so the first push to `main` will
create the `dashboard-latest` release.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added automated, immutable versioned releases for the standalone
dashboard plus a continuously updated “latest” manifest.
* CLI now fetches, verifies, caches, and supports offline fallback for
the dashboard runtime.
* **Bug Fixes**
* Improved dashboard restart behavior to update only when a strictly
newer published version is available.
* **Chores**
* Removed the CLI’s automatic update/re-launch behavior for more
predictable startup.
* **Tests**
* Added unit tests for dashboard manifest validation and latest-version
selection.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
145 lines
6.7 KiB
YAML
145 lines
6.7 KiB
YAML
name: Publish RDE dashboard release
|
|
|
|
# Builds the standalone RDE dashboard and publishes it as a GitHub Release
|
|
# artifact (instead of bundling it into the @hexclave/cli npm tarball). The CLI
|
|
# fetches the newest build at runtime via the `dashboard-latest` manifest. See
|
|
# packages/cli/src/lib/dashboard-release.ts.
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
# Only rebuild/publish when the dashboard or its packaging actually changes.
|
|
# A new release is keyed by the version in apps/dashboard/package.json, so
|
|
# changes outside these paths can't produce a new dashboard build anyway.
|
|
paths:
|
|
- 'apps/dashboard/**'
|
|
- 'packages/cli/scripts/package-dashboard-release.mjs'
|
|
- 'packages/cli/scripts/copy-runtime-assets.mjs'
|
|
- '.github/workflows/dashboard-release.yaml'
|
|
|
|
permissions:
|
|
contents: write # create releases and upload assets
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}
|
|
cancel-in-progress: false # don't interrupt a publish in progress
|
|
|
|
jobs:
|
|
publish-dashboard:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
fetch-depth: 0
|
|
# Don't leave the contents:write token in .git/config for the
|
|
# install/build/package steps; the gh steps below pass GH_TOKEN directly.
|
|
persist-credentials: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
|
with:
|
|
node-version: '22.x'
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build dashboard standalone
|
|
run: pnpm exec turbo run build:rde-standalone --filter=@hexclave/dashboard
|
|
|
|
- name: Package dashboard release
|
|
id: package
|
|
env:
|
|
DASHBOARD_RELEASE_REPO: ${{ github.repository }}
|
|
run: pnpm --filter @hexclave/cli run package-dashboard-release
|
|
|
|
- name: Publish versioned release (immutable)
|
|
id: publish
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ steps.package.outputs.tag }}
|
|
ZIP: ${{ steps.package.outputs.zip }}
|
|
ZIP_NAME: ${{ steps.package.outputs.zip_name }}
|
|
VERSION: ${{ steps.package.outputs.version }}
|
|
EXPECTED_SHA: ${{ steps.package.outputs.sha256 }}
|
|
run: |
|
|
set -euo pipefail
|
|
# A published dashboard-v<version> asset is immutable, so never replace
|
|
# it; advertise the sha of whatever asset is live. (The rebuild isn't
|
|
# byte-reproducible — staged files get fresh mtimes — so we can't just
|
|
# trust EXPECTED_SHA when reusing an existing asset.)
|
|
live_sha="$EXPECTED_SHA"
|
|
if gh release view "$TAG" >/dev/null 2>&1; then
|
|
if gh release view "$TAG" --json assets -q '.assets[].name' | grep -qx "$ZIP_NAME"; then
|
|
tmp="$(mktemp -d)"
|
|
gh release download "$TAG" -p "$ZIP_NAME" -D "$tmp"
|
|
live_sha="$(sha256sum "$tmp/$ZIP_NAME" | cut -d' ' -f1)"
|
|
if [ "$live_sha" != "$EXPECTED_SHA" ]; then
|
|
echo "::warning::$TAG already has a published asset whose sha256 ($live_sha) differs from this rebuild ($EXPECTED_SHA). Releases are immutable, so the existing asset is kept and advertised. A differing hash is expected for nondeterministic rebuilds of identical content; if you intended to ship NEW dashboard content, bump the version in apps/dashboard/package.json."
|
|
else
|
|
echo "Existing $TAG asset matches the build; reusing it."
|
|
fi
|
|
else
|
|
echo "$TAG exists without its asset; uploading."
|
|
gh release upload "$TAG" "$ZIP" --clobber
|
|
fi
|
|
else
|
|
gh release create "$TAG" "$ZIP" \
|
|
--title "RDE dashboard $VERSION" \
|
|
--notes "Standalone RDE dashboard build for \`hexclave dev\` (version $VERSION)."
|
|
fi
|
|
echo "live_sha=$live_sha" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Update floating latest manifest
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
MANIFEST: ${{ steps.package.outputs.manifest }}
|
|
VERSION: ${{ steps.package.outputs.version }}
|
|
LIVE_SHA: ${{ steps.publish.outputs.live_sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Advertise the live asset's sha, which may differ from the rebuild's.
|
|
tmp="$(mktemp)"
|
|
jq --arg s "$LIVE_SHA" '.sha256 = $s' "$MANIFEST" > "$tmp"
|
|
mv "$tmp" "$MANIFEST"
|
|
# dashboard-latest is only ever a plain X.Y.Z release. Refuse to advance
|
|
# it to a prerelease/build-metadata version rather than guess SemVer
|
|
# prerelease ordering in bash (`sort -V` is not a SemVer comparator).
|
|
# The dashboard always ships plain releases, so this should never fire.
|
|
new="${VERSION#v}"
|
|
case "$new" in
|
|
*[-+]*)
|
|
echo "::error::Refusing to publish prerelease/build version $VERSION as dashboard-latest; it must be a plain X.Y.Z release."
|
|
exit 1
|
|
;;
|
|
esac
|
|
if gh release view dashboard-latest >/dev/null 2>&1; then
|
|
# dashboard-latest must only ever move forward. Re-running on an older
|
|
# commit must not roll clients back to a stale dashboard. Re-publishing
|
|
# the same version is allowed (it refreshes the advertised sha).
|
|
published_dir="$(mktemp -d)"
|
|
if gh release download dashboard-latest -p manifest.json -D "$published_dir" 2>/dev/null; then
|
|
published_version="$(jq -r '.version // ""' "$published_dir/manifest.json")"
|
|
# Both sides are plain releases (the published one was gated the same
|
|
# way), so a leading-'v'-stripped, numeric-aware `sort -V` is exact.
|
|
pub="${published_version#v}"
|
|
if [ -n "$pub" ] && [ "$pub" != "$new" ]; then
|
|
newest="$(printf '%s\n%s\n' "$pub" "$new" | sort -V | tail -n1)"
|
|
if [ "$newest" = "$pub" ]; then
|
|
echo "::warning::dashboard-latest already points at $published_version, which is newer than $VERSION; refusing to roll it back."
|
|
exit 0
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
gh release create dashboard-latest \
|
|
--title "RDE dashboard (latest)" \
|
|
--notes "Always points at the newest published RDE dashboard. The manifest.json asset is consumed by the Hexclave CLI at runtime." \
|
|
--latest=false
|
|
fi
|
|
gh release upload dashboard-latest "$MANIFEST" --clobber
|
|
echo "dashboard-latest now points at $VERSION (sha256 $LIVE_SHA)."
|