feat(cli): auto-update RDE dashboard via npx re-exec on stack dev#1521
feat(cli): auto-update RDE dashboard via npx re-exec on stack dev#1521BilalG1 wants to merge 66 commits into
stack dev#1521Conversation
Rebased onto dev after PR 1475 (cl/hexclave-pr1) was squash-merged. Squashes the original 46-commit branch (including PR1-duplicate commits that arrived via cherry-picks/merges) into a single commit containing only PR2's net delta over dev. Original PR 1481 head: 94872de (kept locally at backup/cl-romantic-mendel-5a2c25-pre-rebase)
Source rename across the monorepo. Every publishable package now ships
under its @hexclave/* name natively, no rewrite-at-publish indirection.
Workflow + tooling:
- Delete scripts/rewrite-packages-to-hexclave.ts (one-shot mirror).
- Remove the mirror-publish block from .github/workflows/npm-publish.yaml.
The remaining `pnpm publish -r` step publishes @hexclave/* natively.
- Flip the auto-bump changeset target from @stackframe/stack to
@hexclave/next so 'Update package versions on dev' keeps working.
- Delete packages/template/src/internal/deprecation-warning.ts and its
imports — @hexclave/* never warns about itself, and after PR 3 no
@stackframe/* artifact is ever built from source again.
Package renames (publishable):
@stackframe/react → @hexclave/react
@stackframe/stack → @hexclave/next
@stackframe/js → @hexclave/js
@stackframe/stack-shared → @hexclave/shared
@stackframe/stack-ui → @hexclave/ui
@stackframe/stack-sc → @hexclave/sc
@stackframe/stack-cli → @hexclave/cli
@stackframe/tanstack-start → @hexclave/tanstack-start
@stackframe/dashboard-ui-components → @hexclave/dashboard-ui-components
Internal monorepo packages (private, never published) also renamed for
brand consistency: backend, dashboard, docs, mcp, skills, e2e-tests,
example apps, the swift-sdk, the monorepo root, etc. Cost is mechanical;
payoff is no stray @stackframe/* names left under apps/, examples/, sdks/.
Carve-outs intentionally kept under their legacy names:
- @stackframe/emails — virtual module imported by customer-stored email
templates; the renderer in apps/backend/src/lib/email-rendering.tsx
dual-aliases both names to the same backing module indefinitely.
- @stackframe/template — internal codegen source, never published; per
docs-mintlify/migration.mdx 'internal packages keep names'.
- @stackframe/init-stack — deprecated; now marked private: true so the
last published version on npm continues to serve old install commands
but the workspace stops publishing it.
Backward-compat detection (so projects still on the last @stackframe/*
release keep working):
- packages/stack-shared/src/config-rendering.ts — CONFIG_IMPORT_PACKAGES
table includes both @hexclave/* (canonical, first match wins) and
legacy @stackframe/* names. Function renamed
detectStackframeImportPackage → detectConfigImportPackage.
- apps/dashboard/src/lib/github-config-push.ts — import detection regex
now matches both @hexclave/<name> and @stackframe/<name>, hexclave
preferred.
Versions: every renamed package reset to 1.0.0 in source. The repo's
existing 'bump versions before merging to main' flow will move them to
1.0.1 on the first publish run, so the dual-publish 1.0.0 from PR 2 is
not overwritten.
Other touch-ups discovered during sweep:
- Root package.json: 'fern' script filter was @stackframe/docs (legacy
typo, never resolved) → @hexclave/docs.
- README.md contributor note: @stackframe/XYZ → @hexclave/XYZ.
- packages/stack-cli/package.json: register `hexclave` bin alongside
the legacy `stack` bin so `npx @hexclave/cli init` works on the
natively-published artifact (PR 1481's rewrite script did this at
publish time; now it's in source).
- packages/template/package-template.json: per-platform names + version
flipped to hexclave + 1.0.0 to stay in sync with generated package.json.
- docs/package.json (legacy fumadocs folder, otherwise carved out of the
brand sweep): workspace deps and name updated minimally so `pnpm
install` resolves — content (MDX) intentionally untouched per the
PR 2 scoping decision.
Carve-out files (skipped entirely by the sweep, intentional history):
- docs-mintlify/migration.mdx — teaches the rename, references both.
- RENAME-TO-HEXCLAVE.md — planning doc, references both indefinitely.
- legacy docs/ folder — content untouched per PR 2 carve-out.
generate-sdks regenerated packages/{react,stack,js} from template.
pnpm-lock.yaml regenerated. Typecheck green on stack-shared, stack, js,
react. Dashboard typecheck has pre-existing 'X is of type unknown'
errors that need to be investigated separately (likely a local
node_modules build state issue, not source).
Six fixes from the four parallel reviewers on the rename PR: 1. **Backend .well-known/ routes** — the sweep's directory walker had a bug that skipped every dot-prefixed dir (intended to exclude .git / .turbo / etc.), which also caught Next.js .well-known/ route folders. Two route handlers under apps/backend/.well-known/ still imported @stackframe/stack-shared/dist/* — flipped to @hexclave/shared/dist/*. 2. **Legacy docs/ folder excluded from workspace** — docs/ is the legacy fumadocs site, no longer maintained (replaced by docs-mintlify/). Per user direction, kept on disk for migration reference but dropped from pnpm-workspace.yaml so it no longer gates install / typecheck / lint. This is the right call given the typecheck failures in docs/src/ from the sweep carve-out were never going to be fixed. 3. **Root package.json scripts** — removed every `--filter=@hexclave/docs` reference now that docs/ isn't in the workspace: build:docs (rerouted to @hexclave/docs-mintlify), dev / dev:tui / dev:docs (dropped the filter), and the dead 'fern' script (was @hexclave/docs-only). 4. **build:demo filter** — fixed pre-existing bug where the script filtered package name 'demo-app', but the package is '@hexclave/example-demo-app'. Never resolved before, fixed now. 5. **github-config-push.test.ts legacy fallback** — the sweep flipped the test 'preserves the existing @stackframe/* import package…' from @stackframe/react to @hexclave/react, which made it a duplicate of the test above it and eliminated all coverage of the legacy regex branch in detectImportPackage. Renamed the modified test to reflect what it now tests, and added a new parallel test that feeds an @stackframe/react import and asserts the legacy import is preserved on output. Both branches of the dual-name regex are now covered. 6. **examples/react-example version** — the only package the sweep missed for the 1.0.0 version reset (unscoped name 'react-example' wasn't in the rename map). Bumped 2.8.103 → 1.0.0 for consistency. Verification on a clean install: - `pnpm install --frozen-lockfile` — clean (only pre-existing @vercel/mcp-adapter bin warnings). - `pnpm typecheck` — 28/28 tasks green across the whole workspace. - `pnpm lint` — 28/28 tasks green. Reviewers flagged but I did NOT change (out of scope or non-actionable): - npm-publish.yaml GH Environment name still says 'hexclave/stack-auth' — env names are managed in repo settings, not in YAML; cosmetic. - RENAME-TO-HEXCLAVE.md references the deleted rewrite script — it's a planning doc / historical record, leaving as-is. - code-examples and migration.mdx user-facing references to @stackframe/* — these are documentation that teaches the rename, by design they mention both names.
Three small fixes from the parallel reviewers on PR 3: 1. **tanstack-start-demo vite SSR regex** — examples/tanstack-start-demo/ vite.config.ts:76 had `noExternal: [/^@StackFrame\//, ...]`. The regex was missed by the source rename sweep because it's a regex pattern, not a string literal. After the rename no @stackframe/* package exists in the workspace, so the regex matched nothing and workspace deps (`@hexclave/tanstack-start`, `@hexclave/shared`, `@hexclave/ui`) stopped being inlined for SSR. Without inlining, the Nitro server hits ERR_REQUIRE_ESM on first request because the CJS bundles import ESM-only transitive deps (jose, oauth4webapi). Flipped to /^@hexclave\// to match the renamed packages. 2. **docs.json footer GitHub link** — pointed at hexclave/stack (404, no such repo). The navbar at line 34 already uses hexclave/hexclave per the plan, so aligned the footer to match. 3. **README cleanups**: - Alt text 'Stack Logo' → 'Hexclave Logo' on the header image. - Removed broken /docs/next prefix on the setup-guide link (the actual docs structure is /getting-started/setup, no /docs/next). - contrib.rocks image now points at hexclave/hexclave (was the old stack-auth/stack URL). What I did NOT touch (out of PR 3 scope, surfaced separately for a follow-up doc-pedagogy PR): - 14+ docs-mintlify pages teaching STACK_* env vars instead of HEXCLAVE_* (works via dual-read, but contradicts migration.mdx's 'new code should use HEXCLAVE_*' recommendation). - REST API code samples teaching X-Stack-* headers instead of X-Hexclave-* (works via dual-accept proxy). - docs-mintlify/sdk/objects/stack-app.mdx broken in-page anchors (#stackclientapp etc. — body headings renamed in PR 2 but anchor IDs weren't updated). - MCP server name inconsistency (`stack-auth` in init-prompt.ts vs `hexclave` in dashboard setup-page). - The `ask_stack_auth` MCP tool was removed despite RENAME-TO-HEXCLAVE.md saying it should stay registered as a compat alias indefinitely. - AI prompts (apps/backend/src/lib/ai/prompts.ts, apps/skills/) still teach legacy header / env-var names in generated code examples. All of those are PR 2 pedagogy carry-over, not introduced by this PR, and work functionally via the dual-read/dual-accept compat layers PR 1 + PR 2 put in place. They're documentation polish, not bugs that block PR 3 shipping.
apps/e2e/tests/js/auth-like.test.ts:68 asserts clientApp.version matches `^js @stackframe/js@…`, but the build-time sentinel is stamped from packages/js/package.json's `name` field (configs/tsdown/plugins.ts:10), which PR 3 renamed @stackframe/js → @hexclave/js. Post-rename the sentinel is `js @hexclave/js@1.0.0` (or 1.0.1 after auto-bump), so the old regex fails. Caught by a follow-up reviewer pass — the published-artifact sentinel is the most direct observable that's affected by a source-name rename, and this is the one e2e test that asserts on it directly.
…ertionError suffix The HexclaveAssertionError disclaimer was simplified from "...error in Hexclave (formerly Stack Auth)." to "...error in Hexclave." but the inline snapshots in url-targets and redirect-urls tests still expected the longer text. Updates the template source-of-truth; SDK mirrors regenerate via the preinstall generate-sdks hook.
# Conflicts: # apps/backend/package.json # apps/dashboard/package.json # apps/dev-launchpad/package.json # apps/e2e/package.json # apps/mcp/package.json # apps/mock-oauth-server/package.json # apps/skills/package.json # docs-mintlify/index.mdx # examples/cjs-test/package.json # examples/convex/package.json # examples/demo/package.json # examples/docs-examples/package.json # examples/e-commerce/package.json # examples/js-example/package.json # examples/lovable-react-18-example/package.json # examples/middleware/package.json # examples/react-example/package.json # examples/supabase/package.json # examples/tanstack-start-demo/package.json # packages/dashboard-ui-components/package.json # packages/init-stack/package.json # packages/js/package.json # packages/react/package.json # packages/stack-cli/package.json # packages/stack-sc/package.json # packages/stack-shared/package.json # packages/stack-ui/package.json # packages/stack/package.json # packages/tanstack-start/package.json # packages/template/package-template.json # packages/template/package.json # skills/stack-auth/SKILL.md
# Conflicts: # apps/backend/package.json # apps/dashboard/package.json # apps/dev-launchpad/package.json # apps/e2e/package.json # apps/hosted-components/package.json # apps/internal-tool/package.json # apps/mcp/package.json # apps/mock-oauth-server/package.json # apps/skills/package.json # docs-mintlify/package.json # docs/package.json # examples/cjs-test/package.json # examples/convex/package.json # examples/demo/package.json # examples/docs-examples/package.json # examples/e-commerce/package.json # examples/js-example/package.json # examples/lovable-react-18-example/package.json # examples/middleware/package.json # examples/react-example/package.json # examples/supabase/package.json # examples/tanstack-start-demo/package.json # packages/dashboard-ui-components/package.json # packages/init-stack/package.json # packages/js/package.json # packages/react/package.json # packages/stack-cli/package.json # packages/stack-sc/package.json # packages/stack-shared/package.json # packages/stack-ui/package.json # packages/stack/package.json # packages/tanstack-start/package.json # packages/template/package-template.json # packages/template/package.json # sdks/implementations/swift/package.json # sdks/spec/package.json
Filter `pnpm publish -r` to only the rewritten @hexclave/* packages in the mirror step, removing the reliance on pnpm's skip-existing-versions behavior for the unchanged @stackframe/* packages still in the workspace at that point. Addresses greptile P1 finding on PR #1481.
# Conflicts: # .github/workflows/npm-publish.yaml
…sertionError suffix Companion to ff44d4e — that commit shortened the disclaimer from '...error in Hexclave (formerly Stack Auth).' to '...error in Hexclave.' and updated the url-targets test snapshots, but missed the matching inline snapshot in apps/backend/src/lib/redirect-urls.test.tsx. The test suite passes after the update (33/33).
The rebrand-execution plan is now obsolete: PRs 1, 2, and 3 have shipped the work it described. The doc was a one-shot working document and never intended as long-lived reference material. Note: 9 source-code comments still reference this file (idp.ts:172, tokens.tsx:66, proxy.tsx in backend + dashboard, template/src/index.ts, template/src/lib/stack-app/index.ts). They are intentionally left in place as historical breadcrumbs; they don't affect runtime behavior and can be scrubbed in a follow-up cleanup if desired.
9 source comments pointed at the planning doc removed in the prior commit. Drop the 'See RENAME-TO-HEXCLAVE.md' sentence/parenthetical from each; keep the surrounding substantive context intact (CORS allowlist derivation, JWT issuer dual-acceptance rationale, OIDC opaque-identifier carve-out explanation, Hexclave/Stack alias rationale).
The next.layout-provider check's import regex only matched the legacy @stackframe/stack scope. After the PR-3 source rename, fixtures (and real projects) import StackProvider from @hexclave/next, so the regex returned false, the layout check failed, and the doctor exited 1. Widen the regex to accept both scopes, mirroring the dual-scope detection in packages/stack-shared/src/config-rendering.ts and apps/dashboard/src/lib/github-config-push.ts. Fixes 9 Stack CLI - Doctor failures in apps/e2e/tests/general/cli.test.ts.
# Conflicts: # docs-mintlify/guides/getting-started/ai-integration.mdx # docs-mintlify/guides/going-further/local-development.mdx # docs-mintlify/guides/going-further/local-emulator.mdx # docs-mintlify/guides/other/showcase.mdx
# Conflicts: # apps/skills/src/app/route.ts # packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts # packages/stack-shared/src/interface/page-component-versions.ts
# Conflicts: # docs-mintlify/guides/getting-started/setup.mdx # docs-mintlify/snippets/home-prompt-island.jsx
The bare-name sweep was mutating the sentinel string
('js @stackframe/js@2.8.105') into 'js @hexclave/js@2.8.105' before
the sentinel-specific regex (built from oldName) had a chance to
match, silently leaving the version stuck at the old @StackFrame
version on published @hexclave/* artifacts. Reorder so the sentinel
rewrite runs first; the name sweep that follows can't touch the
rewritten sentinel because it no longer contains any @stackframe/*
substrings.
# Conflicts: # package.json # packages/stack-shared/src/interface/page-component-versions.ts
…cl/hexclave-pr3 # Conflicts: # scripts/rewrite-packages-to-hexclave.ts
Resolve conflicts in init-prompt.ts (take dev's unified-prompt structure, keep Hexclave branding), and rebrand the auto-merged ai-setup-prompt.ts sweep (CLI Python template names, MCP server name/URL/tool, StackConfig type, x-hexclave-* header teach). Regenerated docs-mintlify setup files.
The legacy docs/ folder still referenced noreply@stackframe.co for the shared email provider — flip to match the new sender domain set up on Resend as the dedicated transactional-sender domain. Aligns with the dashboard + docs-mintlify references that were already flipped.
The previous commit passed only `--minimum-release-age=0`, but npm's actual config key is `min-release-age` (days, npm >=11.10.0) — CLI flag `--min-release-age`. Pass that, plus the camelCase `--minimum-release-age=0` defensively (the spelling pnpm/bun use, in case npx is shimmed). npm ignores unrecognized config flags, so the extra one is harmless. Without the correct flag, npx of a version newer than a user's global cooldown window fails with ETARGET and would kill `stack dev`.
We only ever invoke `npx` (the npm binary), whose cooldown config is `min-release-age`. The camelCase `--minimum-release-age` is pnpm/bun's spelling, and those aren't reached via npx (they use `pnpm dlx` / `bunx`), so the extra flag was dead weight npm silently ignored. Keep only `--min-release-age=0`.
Greptile SummaryThis PR adds an auto-update mechanism to
Confidence Score: 3/5The auto-update and registry-cache paths are safe and well-tested, but After SIGTERM succeeds, a recycled pid owned by the same user keeps packages/stack-cli/src/commands/dev.ts — specifically the Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant CLI as stack dev (installed)
participant Registry as npm registry
participant npx as npx @latest
participant Dashboard as bg dashboard
User->>CLI: stack dev --config-file
CLI->>Registry: GET /latest (TTL-cached, 1.5s timeout)
Registry-->>CLI: version 2.8.110
CLI->>CLI: isVersionNewer check
CLI->>npx: "spawn npx -p @hexclave/cli@2.8.110 stack dev"
npx-->>CLI: exits with child code
npx->>Dashboard: isDashboardReachable port 26700?
alt stale dashboard running
npx->>Dashboard: shouldRestartDashboard check
npx->>Dashboard: SIGTERM then wait then SIGKILL
npx->>Dashboard: spawn new dashboard detached
else up-to-date or no dashboard
npx->>Dashboard: reuse or start normally
end
Dashboard-->>User: http://127.0.0.1:26700
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
packages/stack-cli/src/commands/dev.ts:286-301
**SIGKILL can reach a recycled pid owned by the same user**
After a successful `SIGTERM`, the wait loop calls `processExists(pid)`, which probes with `kill(pid, 0)`. If the dashboard exits quickly and the pid is immediately reused by a process also owned by the current user, `kill(pid, 0)` succeeds (no exception), `processExists` returns `true`, the loop spins for the full 10-second timeout, and the subsequent `process.kill(pid, "SIGKILL")` kills an unrelated process. The EPERM short-circuit in the SIGTERM block only defends against recycling onto *another user's* process; it has no effect once SIGTERM is already sent and we're in the polling loop.
The port availability check is the property that actually matters for letting the new dashboard bind — gate the early-return on that alone, or additionally accept `!processExists(pid)` only when the result also matches the EPERM/ESRCH path.
### Issue 2 of 2
packages/stack-cli/src/lib/dev-env-state.ts:59
**`cliUpdateCheck` is passed through without type validation**
`parsed.cliUpdateCheck` is assigned as-is without verifying that it has the expected shape (`{ packageName: string, latestVersion: string, checkedAtMillis: number }`). If the state file is hand-edited or written by a different version, `readCliUpdateCheckCache` returns a structurally wrong object and `resolveLatestVersion` could see `NaN` from `now - cache.checkedAtMillis` or skip the cache packageName check unexpectedly. Adding a type guard matching `CliUpdateCheckCache` would keep the existing safe-fallback behaviour explicit rather than accidental.
Reviews (1): Last reviewed commit: "fix(cli): drop redundant --minimum-relea..." | Re-trigger Greptile |
There was a problem hiding this comment.
1 issue found across 12 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
Three unresolved review findings on the auto-update feature: - killLocalDashboard (greptile P1): after SIGTERM, the wait loop gated on processExists(pid), which false-positives if the dashboard exits and its pid is recycled onto another same-user process — spinning the full timeout and then SIGKILLing the unrelated process. Gate purely on the port being freed (the property that actually lets the replacement bind). SIGKILL is now only reached when the port is still occupied, i.e. the process is still up and the pid is necessarily valid. - dev-env-state (greptile P2): validate the on-disk cliUpdateCheck cache shape on read; a non-string latestVersion would otherwise reach version parsing and throw. Malformed entries are treated as "no cache". - maybeReexecToLatest (cubic P2): documented "fail open" but readDevEnvState could throw (corrupt/bad-permission state file) and crash `stack dev`. Wrap the body in try/catch so any unexpected error falls through to the installed CLI. Tests: +4 — recycled-pid early-return without SIGKILL, malformed/well-formed cache read, and fail-open on a corrupt state file. 170 pass.
What & why
Re-running
stack dev/hexclave devnow picks up the latest published dashboard without reinstalling the CLI.In the RDE, the dashboard is a Next.js standalone build bundled into the
@hexclave/clinpm tarball — so a dashboard change only reaches a user when they get a newer CLI version. This PR closes that gap for the recommendedstack devflow.How it works
npx self-re-exec — at the top of the
devaction, the CLI checks npm for a newer@hexclave/cli. If found, it re-execsnpx --yes -p @hexclave/cli@<latest> stack dev <your args>(with a loop guard) and exits with the child's code. The running code — and the dashboard bundled in that tarball — is now the latest; the user's installed devDependency is untouched. npx caches per version, so steady-state runs are fast.Dashboard version handshake (the necessary second half) —
stack devkeeps a detached background dashboard alive across runs and reuses it by default, which would otherwise silently defeat the update. The now-latest process compares the running dashboard's version (persisted in dev-env state) against its own and kills + restarts the stale one (SIGTERM → wait → SIGKILL) so the new dashboard actually binds:26700. Equal/older/unknown versions are reused exactly as before.Safety / opt-outs
STACK_CLI_SKIP_AUTO_UPDATE, loop guard), when the user opts out (STACK_CLI_NO_AUTO_UPDATE/--no-auto-update), and in CI (CI).isVersionNewernever downgrades and returns false for unparseable versions.Changes
packages/stack-cli/src/lib/self-update.ts(new) —maybeReexecToLatest(),resolveLatestVersion(),isVersionNewer(),buildNpxInvocation().packages/stack-cli/src/commands/dev.ts— re-exec wiring,killLocalDashboard(), version handshake,--no-auto-updateflag, version stamp on the recorded dashboard process.packages/stack-cli/src/lib/dev-env-state.ts—localDashboard.version+cliUpdateCheckcache helpers.self-update.test.ts+ additions todev-env-state.test.ts.Verification
pnpm --filter @hexclave/cli run lint✅pnpm --filter @hexclave/cli run typecheck✅pnpm --filter @hexclave/cli run test✅ (132 passed)Prerequisite
Relies on
@hexclave/clibeing published to npm with thelatestdist-tag tracking releases — otherwise the check is a no-op (which is safe).Summary by cubic
stack devnow re-execs via npx to use the latest published@hexclave/cli, keeping the bundled RDE dashboard up to date without reinstalling. It also restarts a stale background dashboard so the new version binds port 26700.New Features
@hexclave/cliand re-runsnpx --yes --min-release-age=0 -p <pkg>@<latest> stack dev .... Cached lookup, short timeout, offline-safe. Skips in CI and when--no-auto-updateorSTACK_CLI_NO_AUTO_UPDATE=1is set.Bug Fixes
--min-release-age=0so just-published versions install reliably, avoiding ETARGET.cliUpdateCheckshape on read; drop malformed entries to avoid version-parse errors.stack dev; continue with the installed CLI.Written for commit d3f0581. Summary will update on new commits.