feat(hexclave): PR 2 — visible rebrand (Hexclave brand goes public) (#1481)
all-good: Did all the other checks pass? / all-good (push) Has been cancelled
Ensure Prisma migrations are in sync with the schema / check_prisma_migrations (22.x) (push) Has been cancelled
DB migration compat / Check if migrations changed (push) Has been cancelled
Docker Server Build and Push / Docker Build and Push Server (push) Has been cancelled
Docker Server Build and Run / docker (push) Has been cancelled
Runs E2E API Tests (Local Emulator) / E2E Tests (Local Emulator, Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (mock, 22.x) (push) Has been cancelled
Runs E2E API Tests / E2E Tests (Node ${{ matrix.node-version }}, Freestyle ${{ matrix.freestyle-mode }}) (prod, 22.x) (push) Has been cancelled
Runs E2E API Tests with custom port prefix / build (22.x) (push) Has been cancelled
Runs E2E Fallback Tests / E2E Fallback Tests (Node ${{ matrix.node-version }}) (22.x) (push) Has been cancelled
Lint & build / lint_and_build (24) (push) Has been cancelled
TOC Generator / TOC Generator (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled

## Summary

**Stacked on [#1475](https://github.com/hexclave/stack-auth/pull/1475)**
(`cl/hexclave-pr1`, the invisible compatibility layer). Diff vs that
base = the actual PR 2 code.

This is **PR 2 of the Stack Auth → Hexclave rebrand: the visible flip**.
Old wire identifiers (cookies, request/response headers, Bearer prefix,
JWT issuers, MCP tool name) keep working indefinitely via PR 1's
dual-accept. This PR flips every user-visible surface — package names
taught in docs, SDK class names in code examples, dashboard setup
snippets, page titles, error messages, email content, CLI binary,
default base URLs, GitHub repo slug, contributor guidance — to the
Hexclave brand.

See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 2: Rebrand
to Hexclave (visible)"* for the full per-work-area spec.

## What's implemented (per the plan's PR 2 scope)

- **SDK base URLs** flipped: `defaultBaseUrl` and
`defaultAnalyticsBaseUrl` in
[common.ts](packages/template/src/lib/stack-app/apps/implementations/common.ts:127)
→ `https://api.hexclave.com` / `https://r.hexclave.com`. PR 1's
[`getHardcodedFallbackUrls`](packages/stack-shared/src/utils/urls.tsx:199)
table now keys on the Hexclave domain.

- **Domain inventory sweep** (16 subdomains from the plan): every
`api/app/docs/discord/demo/mcp/skill/feedback/test/preview/r/api2/api.staging/idp-jwk-audience/built-with.stack-auth.com`
reference in production code, docs-mintlify, examples, READMEs, and
contributor guidance flipped to `*.hexclave.com`. Carve-outs: PR 1's
intentional JWT issuer dual-accept table in
[tokens.tsx](apps/backend/src/lib/tokens.tsx), the legacy `./docs/`
folder, the `unified-docs-widget` allowlist (deliberately accepts both
during DNS transition), and `url-targets.ts` hosted-component default
(baked into existing customer deploys).

- **`@deprecated` JSDoc** on every `Stack*` public export
([packages/template/src/lib/stack-app/index.ts](packages/template/src/lib/stack-app/index.ts)
+ [packages/template/src/index.ts](packages/template/src/index.ts)) —
`StackClientApp`, `StackServerApp`, `StackAdminApp` + every
constructor/options/JSON type, `StackHandler`, `StackProvider`,
`StackTheme`, `useStackApp`, `defineStackConfig`, `StackConfig`.
Hexclave\* aliases are now canonical.

- **Runtime `console.warn`**
([packages/template/src/internal/deprecation-warning.ts](packages/template/src/internal/deprecation-warning.ts))
— once-per-process when the SDK is loaded from a `@stackframe/*`
artifact. Detection uses the existing
`STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL` (rewritten at build
time to e.g. `js @stackframe/[email protected]` or `js
@hexclave/[email protected]`); `@hexclave/*` mirror artifacts short-circuit the
warning.

- **Tier 3 data migration**: new idempotent SQL migration
[`20260523000000_rename_internal_project_to_hexclave`](apps/backend/prisma/migrations/20260523000000_rename_internal_project_to_hexclave/migration.sql)
— updates the internal Project `displayName` 'Stack Dashboard' →
'Hexclave Dashboard' and `description` only if both still hold the
pre-rebrand defaults. Operator-renamed projects untouched, missing row
no-ops, re-runs are no-ops. [`seed.ts`](apps/backend/prisma/seed.ts:87)
default flipped. `getSharedEmailConfig("Stack Auth")` → `("Hexclave")`.

- **Tier 4 brand strings** (mechanical sweep, ~340 files):
- Page + OpenAPI titles (Hexclave API / Dashboard / REST API / Webhooks
API / Documentation). OpenAPI `info.description` documents
`X-Hexclave-*` headers as canonical with compat note on `X-Stack-*`.
- `HexclaveAssertionError` message text
([errors.tsx:71](packages/stack-shared/src/utils/errors.tsx:71)) — "an
error in Stack." → "an error in Hexclave."
- Known-error message templates
([known-errors.tsx](packages/stack-shared/src/known-errors.tsx)) flipped
to lead with `x-hexclave-*` + the new `docs.hexclave.com` URL; legacy
`x-stack-*` mentioned as compat aliases. **25 e2e test files updated in
lockstep**.
- Email content: failed-emails-digest body, sendTestEmail recipient (now
`sent-with-hexclave.com`), test-email-recipient default.
  - `CHANGELOG.md` title → "Hexclave Changelog".
- `AGENTS.md` env var convention: new vars prefix `HEXCLAVE_` /
`NEXT_PUBLIC_HEXCLAVE_` for Category A/B; legacy `STACK_*` explicitly
noted as accepted via PR 1's dual-read.

- **CLI / init wizard**:
- Every dashboard setup snippet, init-stack template, and docs-mintlify
page teaches `npx @hexclave/cli@latest init` (was
`@stackframe/stack-cli`).
[setup-page.tsx](apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx)
+
[link-existing-onboarding](apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx).
- [init-stack](packages/init-stack/src/index.ts:634)
`STACK_*_INSTALL_PACKAGE_NAME_OVERRIDE` defaults flipped to
`@hexclave/*`.
- Generated `stack/client.ts` / `stack/server.ts` import from
`@hexclave/next` and reference `HexclaveClientApp` /
`HexclaveServerApp`.
- Internal `StackAuthKeys` dashboard component renamed to
`HexclaveKeys`.

- **docs-mintlify rewrite** (legacy `./docs/` intentionally untouched
per scoping decision):
- **78 MDX files swept**.
`@stackframe/{react,stack,js,tanstack-start,...}` →
`@hexclave/{react,stack,js,...}` in install snippets and code blocks;
`Stack*` SDK class names → `Hexclave*` in all code examples; 'Stack
Auth' brand phrase → 'Hexclave'.
- `openapi/{server,admin,client,webhooks}.json` titles → 'Hexclave REST
API' / 'Hexclave Webhooks API'.

- **Generators flipped before regeneration**:
-
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts),
[`/ai/prompts.ts`](packages/stack-shared/src/ai/prompts.ts),
[`apps/backend/src/lib/ai/prompts.ts`](apps/backend/src/lib/ai/prompts.ts),
[`apps/backend/src/lib/ai/tools/create-email-{template,draft}.ts`](apps/backend/src/lib/ai/tools/create-email-template.ts),
[`apps/skills/src/app/route.ts`](apps/skills/src/app/route.ts) (taught
MCP tool → `ask_hexclave` with compat note; CLI binary teach →
`hexclave`),
[`docs-mintlify/snippets/home-prompt-island.jsx`](docs-mintlify/snippets/home-prompt-island.jsx),
[`packages/template/README.md`](packages/template/README.md) +
integrations/convex/component/README.md.
  - `generate-sdks` propagated changes to `packages/{react,stack,js}`.

- **OpenAPI dual-documentation**:
[`apps/backend/src/app/api/latest/route.ts`](apps/backend/src/app/api/latest/route.ts)
now lists `X-Hexclave-*` headers as primary documented schemas with
`X-Stack-*` duplicates marked `.optional()` (both accepted at runtime by
PR 1's normalize-at-proxy shim).

- **`@stackframe/emails` virtual module**: dual-aliased to
`@hexclave/emails` at the bundler boundary
([email-rendering.tsx:89](apps/backend/src/lib/email-rendering.tsx:89)).
Stored email templates continue to import from either name; new
AI-generated templates and the system prompt teach `@hexclave/emails`.

- **Tier 2 mirror-publish wiring** (new this PR, lays the groundwork for
`@hexclave/*` first publish):
-
[`scripts/rewrite-packages-to-hexclave.ts`](scripts/rewrite-packages-to-hexclave.ts)
— rewrites 9 publishable `@stackframe/*` → `@hexclave/*` `package.json`
files (reads `HEXCLAVE_VERSION` env or `--version=` flag), pins
cross-deps to the shared `@hexclave` version, registers `hexclave` bin
alongside `stack` for `@hexclave/cli`.
-
[`.github/workflows/npm-publish.yaml`](.github/workflows/npm-publish.yaml)
appended with rewrite-then-republish step. `pnpm publish` skips
already-on-npm versions so reruns are safe.

- **Sender email domain**: `[email protected]` →
`[email protected]` (the dedicated transactional-sender
domain split per the plan, to isolate bulk deliverability from
`hexclave.com` reputation); `security@` / `[email protected]` inbound
mailboxes → `@hexclave.com`.

- **Self-host docs**: docker network / container names in the bash
examples flipped from `stack-auth` to `hexclave` (`hexclave-postgres`,
`hexclave-clickhouse`, `hexclave.env`). The docker image tag
`stackauth/server:latest` stays per the plan's locked decision.

- **GitHub repo slug**: `hexclave/stack-auth` → `hexclave/hexclave` in
every `package.json` `repository` field, README link, CHANGELOG
raw-asset URL.

## Carve-outs (deliberately untouched)

-
**[`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)**
JWT issuer dual-accept table — PR 1 intentional infrastructure, kept
indefinitely.
- **Legacy `./docs/` folder** — per scoping decision (only
`docs-mintlify/` rewritten).
- **`unified-docs-widget` hostname allowlist** — accepts both
`.hexclave.com` (canonical) and `.stack-auth.com` (transition window)
for DNS rollout.
- **`url-targets.ts`** hosted-domain default
`.built-with-stack-auth.com` — wire identifier baked into existing
customer deploys; indefinite read-fallback.
- **Binary visual assets** (logos, favicons, OG images, README
screenshots) — out of scope for this PR. Need design work; tracked
separately.

## Verification

- **`pnpm typecheck`** on
`packages/{template,stack-shared,react,stack,js}` + `apps/dashboard`:
**all green**. The remaining backend / e-commerce-demo typecheck errors
are pre-existing (Prisma codegen output +
`./generated/api-versions.json` not present in fresh worktrees without
`pnpm run codegen-prisma` + a live DB) and unrelated to this diff.
- **`pnpm lint`** on the same 6 packages: all green.
- **Final grep** for residual `Stack Auth` / `stack-auth.com` /
`@stackframe/stack-cli@latest` references: zero outside the intentional
carve-outs above.
- **25 e2e test files updated in lockstep** with the known-error message
changes (asserted strings flipped to match the new x-hexclave-* +
compat-note messages).

## Deploy blockers (ops sequencing before this rebrand goes live)

This PR is code-complete, but the rebrand's visible surfaces (SDK
default URLs, dashboard links, npm READMEs, REST error messages, runtime
deprecation warning) all point at `*.hexclave.com` / `@hexclave/*`
resources that don't exist yet. None of these are fixable from a PR —
they're ops/registrar/npm work that has to be sequenced before merging
this to a release tag.

Suggested ordering, hardest blockers first:

### Tier 1 — required before customer-facing deploy (everything below
this line *will visibly break customers on day 1* if skipped)

1. **DNS + TLS for `api.hexclave.com` + `api1./api2.hexclave.com`** →
must point at the same backend that serves `api.stack-auth.com` (or a
backend that mirrors PR 1's dual-accept). The SDK's new `defaultBaseUrl`
is `https://api.hexclave.com`; every customer that relied on the old
default and upgrades to a post-PR2 SDK build sends API requests here.
Until this resolves, every default-config customer's API call NXDOMAINs.
2. **DNS for `app.hexclave.com`** → the dashboard. Referenced in the
SDK's default-error messages ("Please create a project on the Hexclave
dashboard at https://app.hexclave.com"), the init-stack flow's
`wizard-congrats` redirect, and the OAuth dashboard handoff.
3. **DNS for `docs.hexclave.com`** + Mintlify deploy → the SDK runtime
deprecation warning (`https://docs.hexclave.com/migration`), every
README, every "Learn more" link in the dashboard, and every REST API
error body (`/api/overview#authentication`) points here. The MDX is in
this PR; the docs build target needs DNS.
4. **DNS for `mcp.hexclave.com`** → the MCP server endpoint that every
taught agent integration (`claude mcp add ...`, `cursor`, `codex`,
`vscode`) registers. Until this resolves, every `npx
@hexclave/cli@latest init` MCP-registration step fails.
5. **Reserve the `@hexclave` npm scope + set repo variable
`HEXCLAVE_VERSION`** → the mirror-publish step in
`.github/workflows/npm-publish.yaml` is gated on this variable. Without
it, the entire taught onboarding command `npx @hexclave/cli@latest init`
404s from the npm registry, *and* every README that says "install
`@hexclave/next`" leads to install failure. Pick the initial version
intentionally (`1.0.0` or aligned to `@stackframe/stack`); don't accept
a silent default.

### Tier 2 — required before announcing the rebrand publicly (lookalike
or low-traffic surfaces, but visibly broken)

6. **DNS for `r.hexclave.com`** → the analytics beacon
`defaultAnalyticsBaseUrl`. Silent failure if missing (analytics drops),
but should land alongside Tier 1.
7. **Register `sent-with-hexclave.com` + full email auth (SPF / DKIM /
DMARC)** → the new default sender domain for shared-sender transactional
emails. Without it the dashboard "send test email" path emits bounces,
and shared-sender flows (`getSharedEmailConfig("Hexclave")`) deliver to
spam at best.
8. **MX + SPF / DMARC for `hexclave.com`** → `[email protected]` and
`[email protected]` mailboxes. The security disclosure mailbox is
referenced in [`.github/SECURITY.md`](.github/SECURITY.md);
`[email protected]` is the actual recipient of internal feedback emails
sent at runtime by
[`apps/backend/src/lib/internal-feedback-emails.tsx`](apps/backend/src/lib/internal-feedback-emails.tsx).
Today, every runtime feedback email bounces.
9. **DNS for `skill.hexclave.com`** → the canonical AI-agent skill fetch
URL (the agent bootstrap pivot). Without it, the entire "agent downloads
`SKILL.md` from a known URL" flow taught in
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts)
fails.
10. **Create `github.com/hexclave/hexclave` as a public repo** (even as
a redirect to `hexclave/stack-auth`) **OR** rewrite every `package.json`
`"repository"` field + dashboard footer "view on GitHub" link to point
at `hexclave/stack-auth` (which already exists). Currently every npm
package page's "Repository" link is dead, and the dashboard's GitHub
button + dev-tool repo link are dead.

### Tier 3 — broken but low-visibility / low-traffic

11. **DNS for `discord.hexclave.com`** → Discord invite redirect, used
in every README's chip and the dashboard footer.
12. **DNS for `demo.hexclave.com`** → " Demo" badge in every npm
package README. Broken-image badge on the package page.
13. **DNS + TLS for `built-with-hexclave.com`** → optional
hosted-handler domain (the default reverted to
`.built-with-stack-auth.com` in this PR's carve-outs, so this only
matters for projects that manually flip).

## Other follow-ups (not deploy-blocking)

- **E2E snapshot regen across the full suite** for the dual-emitted
`x-hexclave-*` response headers (PR 1 follow-up; `vitest -u` in CI
absorbs).
- **Binary visual assets** — logos, favicons, OG images, README
screenshots; need design pass.
- **Backend OpenAPI fumadocs regen** in CI flow — the JSON files in
`docs-mintlify/openapi/` are committed but regen runs in CI. Verify the
workflow that does this still works against the post-PR2 source.
- **Backend typecheck infra debt** — needs `codegen-prisma` +
`codegen-route-info` to clear; pre-existing, unaffected by this PR.

## Test plan

- [ ] CI runs full e2e suite (with `vitest -u` to absorb residual
snapshot deltas, then committed back).
- [ ] Spot-check: new `@hexclave/cli init` (once published) generates
`hexclave.config.ts` and works against a fresh project.
- [ ] Spot-check: existing customer with `@stackframe/stack` import sees
the once-per-process `console.warn` recommending `@hexclave/next` on SDK
init.
- [ ] Manual: dashboard setup page renders the `npx @hexclave/cli@latest
init` snippet and the `x-hexclave-publishable-client-key` API header in
the curl example.
- [ ] Manual: a fresh `pnpm run prisma migrate` against a clean DB sets
the internal project displayName to 'Hexclave Dashboard'.

---------

Co-authored-by: Konstantin Wohlwend <[email protected]>
This commit is contained in:
BilalG1
2026-05-26 19:18:20 -07:00
committed by GitHub
co-authored by Konstantin Wohlwend
parent a27e4d9bb5
commit 57ff5d3ce9
401 changed files with 3480 additions and 2359 deletions
+2 -2
View File
@@ -258,7 +258,7 @@ function renderTabPanels() {
function renderUnifiedAiPromptTab() {
return deindent`
<Tab title="${unifiedAiPromptTabTitle}">
Setting up with AI? Use this single prompt in your coding agent to set up Stack Auth for your selected stack.
Setting up with AI? Use this single prompt in your coding agent to set up Hexclave for your selected stack.
<div className="not-prose relative mt-3">
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded-2xl border border-[#cdd7f4] bg-white/75 px-4 py-3 pr-32 font-mono text-xs leading-6 text-zinc-700 backdrop-blur-sm sm:text-sm dark:border-[#33476d] dark:bg-black/20 dark:text-zinc-200"><code>{generatedSetupPromptText}</code></pre>
@@ -300,7 +300,7 @@ writeFileSyncIfChanged(
deindent`
---
title: Setup
description: Install and configure Stack Auth for your project
description: Install and configure Hexclave for your project
sidebarTitle: Setup
---
+217
View File
@@ -0,0 +1,217 @@
/**
* Rewrite-then-republish: in-place mutate each publishable `@stackframe/*`
* package.json into the `@hexclave/*` mirror name, AND rewrite every
* `@stackframe/*` reference inside `dist/` (bundled `require()` / `import`
* specifiers + the build-time package-version sentinel) so the published
* `@hexclave/*` artifacts resolve their cross-package deps against the
* `@hexclave/*` mirror packages we just renamed. `pnpm publish -r` picks
* them up again on the next workflow step. The workflow runs on a clean
* checkout each time, so no revert is needed.
*
* Mapping per RENAME-TO-HEXCLAVE.md (Tier 2). All mirror packages share
* one version (read from HEXCLAVE_VERSION env or `--version <x>`); cross-
* package deps are pinned to that exact version since they're a single
* substitution.
*
* The `@hexclave/cli` mirror additionally registers a `hexclave` bin
* alongside `stack` so `npx @hexclave/cli@latest init` works.
*
* Not mirrored (per the plan): `@stackframe/template` (codegen source),
* `@stackframe/init-stack` (kept under existing name; new-user onboarding
* moves to the CLI's `init` subcommand).
*/
import fs from "node:fs";
import path from "node:path";
// Source @stackframe/* name → target @hexclave/* name.
// Special-cased: @stackframe/stack (the Next.js-specific SDK) publishes as
// @hexclave/next under the new brand, mirroring how @hexclave/react and
// @hexclave/js identify the framework they target. The dist-content rewriter
// below propagates this through every cross-package require/import specifier
// and the build-time package-version sentinel.
const PACKAGE_NAME_MAP: Record<string, string> = {
"@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",
};
// Directories under packages/ that hold the publishable @stackframe/* packages.
const PACKAGE_DIRS = [
"packages/react",
"packages/stack",
"packages/js",
"packages/stack-shared",
"packages/stack-ui",
"packages/stack-sc",
"packages/stack-cli",
"packages/tanstack-start",
"packages/dashboard-ui-components",
];
function getHexclaveVersion(): string {
const arg = process.argv.find((a) => a.startsWith("--version="));
const version = arg ? arg.split("=")[1] : process.env.HEXCLAVE_VERSION;
if (!version || !/^\d+\.\d+\.\d+/.test(version)) {
throw new Error(
"rewrite-packages-to-hexclave: pass --version=X.Y.Z or set HEXCLAVE_VERSION.",
);
}
return version;
}
function rewriteDepsObject(
deps: Record<string, string> | undefined,
hexclaveVersion: string,
): Record<string, string> | undefined {
if (!deps) return deps;
const out: Record<string, string> = {};
for (const [name, spec] of Object.entries(deps)) {
if (PACKAGE_NAME_MAP[name]) {
out[PACKAGE_NAME_MAP[name]] = hexclaveVersion;
} else {
out[name] = spec;
}
}
return out;
}
function rewritePackage(dir: string, hexclaveVersion: string): void {
const pkgPath = path.join(dir, "package.json");
if (!fs.existsSync(pkgPath)) {
console.log(`skip: ${pkgPath} does not exist`);
return;
}
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
const oldName: string = pkg.name;
const oldVersion: string = pkg.version;
const newName = PACKAGE_NAME_MAP[oldName];
if (!newName) {
console.log(`skip: ${oldName} not in mirror map`);
return;
}
pkg.name = newName;
pkg.version = hexclaveVersion;
pkg.dependencies = rewriteDepsObject(pkg.dependencies, hexclaveVersion);
pkg.peerDependencies = rewriteDepsObject(pkg.peerDependencies, hexclaveVersion);
pkg.devDependencies = rewriteDepsObject(pkg.devDependencies, hexclaveVersion);
pkg.optionalDependencies = rewriteDepsObject(pkg.optionalDependencies, hexclaveVersion);
// The CLI gets a hexclave bin alias alongside the existing stack one, so
// `npx @hexclave/cli@latest init` is the new taught entrypoint.
if (newName === "@hexclave/cli" && pkg.bin && typeof pkg.bin === "object") {
if (pkg.bin.stack && !pkg.bin.hexclave) {
pkg.bin = { hexclave: pkg.bin.stack, ...pkg.bin };
}
}
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
console.log(`rewrote: ${oldName}${newName}@${hexclaveVersion}`);
// Rewrite cross-package require()/import specifiers and the build-time
// package-version sentinel inside dist/. tsdown bundles peer/shared deps
// as external `require("@stackframe/...")` calls — without this rewrite,
// installing only @hexclave/* leaves those requires unresolvable at runtime.
rewriteDistFiles(dir, oldName, oldVersion, hexclaveVersion);
}
// Bundled artifacts contain literal package-name strings (require/import
// specifiers, the build-time `js <pkg>@<ver>` sentinel, occasional source-hint
// strings). Rewriting them in lockstep with the package.json rename keeps the
// published @hexclave/* artifacts self-consistent.
function rewriteDistFiles(
dir: string,
oldName: string,
oldVersion: string,
hexclaveVersion: string,
): void {
const distDir = path.join(dir, "dist");
if (!fs.existsSync(distDir)) {
console.log(` no dist/ to rewrite under ${dir}`);
return;
}
// Longest names first so e.g. `@stackframe/stack-shared` doesn't get
// half-replaced by the shorter `@stackframe/stack` prefix.
const sortedMappings = Object.entries(PACKAGE_NAME_MAP).sort(
(a, b) => b[0].length - a[0].length,
);
let totalFiles = 0;
let touchedFiles = 0;
const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// Match the build-time `js <oldName>@<oldVersion>` sentinel exactly.
const sentinelPattern = new RegExp(
`js ${escapeRegex(oldName)}@${escapeRegex(oldVersion)}`,
"g",
);
const newSentinel = `js ${PACKAGE_NAME_MAP[oldName]}@${hexclaveVersion}`;
const walk = (d: string) => {
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
const p = path.join(d, entry.name);
if (entry.isDirectory()) {
walk(p);
continue;
}
if (!entry.isFile()) continue;
// Skip binary artifacts; only rewrite text files the bundler produced.
// Sourcemaps (.map) are intentionally excluded: they embed original file
// paths and (when sourcesContent is set) the source text — a blanket
// string replace inside them would corrupt the mappings and break
// production-error debugging. The code references that actually need
// rewriting all live in the .js/.cjs/.d.ts compiled output.
if (!/\.(?:m?js|cjs|d\.m?ts|d\.cts|json|html|txt|md)$/.test(entry.name)) continue;
totalFiles += 1;
const original = fs.readFileSync(p, "utf-8");
let updated = original;
// Rewrite the build-time package-version sentinel FIRST, before the
// bare-name sweep below. The sentinel encodes both the package name
// AND the package version (`js @stackframe/[email protected]`) and we need
// to bump both halves in lockstep. If the name sweep ran first it
// would rewrite just the name half (→ `js @hexclave/[email protected]`),
// and then this sentinel-specific regex — built from `oldName` —
// would no longer match anything in `updated`, silently leaving
// the version stuck at the old @stackframe version. Doing the
// sentinel rewrite first produces the final string in one shot;
// the name sweep that follows won't touch it because the rewritten
// sentinel contains no `@stackframe/*` substrings to match.
updated = updated.replace(sentinelPattern, newSentinel);
for (const [oldPkg, newPkg] of sortedMappings) {
if (!updated.includes(oldPkg)) continue;
// Replace the bare package name as a whole token. Subpaths
// (`@stackframe/stack-shared/dist/utils/errors`) trail naturally.
const pattern = new RegExp(escapeRegex(oldPkg), "g");
updated = updated.replace(pattern, newPkg);
}
if (updated !== original) {
fs.writeFileSync(p, updated);
touchedFiles += 1;
}
}
};
walk(distDir);
console.log(` rewrote dist/: ${touchedFiles}/${totalFiles} files in ${dir}`);
}
function main(): void {
const hexclaveVersion = getHexclaveVersion();
const repoRoot = path.resolve(__dirname, "..");
for (const rel of PACKAGE_DIRS) {
rewritePackage(path.join(repoRoot, rel), hexclaveVersion);
}
}
main();