mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Remove dead dual-publish wiring: rewrite script + deprecation warning
Delete scripts/rewrite-packages-to-hexclave.ts and the SDK deprecation-warning side-effect module (plus its import in the template entrypoint). With packages renamed to @hexclave/* in source, no @stackframe/* artifact is built, so the runtime warning has nothing to detect and the publish-time rewrite step is gone.
This commit is contained in:
parent
d5954a4b27
commit
3cf4e926d3
@ -1,9 +1,3 @@
|
||||
// Side-effect import — fires once-per-process @stackframe/* deprecation warning at SDK load time.
|
||||
// In packages published as @hexclave/*, scripts/rewrite-packages-to-hexclave.ts rewrites the
|
||||
// build-time sentinel inside dist/ (`js @stackframe/<pkg>@<ver>` → `js @hexclave/<new>@<ver>`),
|
||||
// so the @stackframe/-prefixed check in deprecation-warning.ts short-circuits there.
|
||||
import "./internal/deprecation-warning";
|
||||
|
||||
export * from './lib/stack-app';
|
||||
export { getConvexProvidersConfig } from "./integrations/convex";
|
||||
// Hexclave aliases and legacy Stack* names — @deprecated JSDoc lives on the original
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
// PR 2 — Hexclave rebrand. When the SDK is loaded from a legacy `@stackframe/*`
|
||||
// package, emit a once-per-process console.warn pointing to the `@hexclave/*`
|
||||
// equivalent. The published `@hexclave/*` packages reuse the exact same built
|
||||
// artifacts, so we detect the package brand at runtime from `clientVersion`
|
||||
// (which the tsdown plugin stamps from package.json at build time).
|
||||
//
|
||||
// Repeated SDK imports within one process are de-duplicated via a Symbol on
|
||||
// globalThis — multiple bundlers / dynamic imports won't spam the console.
|
||||
|
||||
import { clientVersion } from "../lib/stack-app/apps/implementations/common";
|
||||
|
||||
const WARNED_SYMBOL = Symbol.for("Hexclave--stackframe-package-deprecation-warned");
|
||||
|
||||
function shouldWarn(): boolean {
|
||||
// clientVersion is "js <package-name>@<version>" once the build-time sentinel
|
||||
// is rewritten. In a built `@stackframe/*` artifact this looks like
|
||||
// "js @stackframe/stack@2.8.92"; in `@hexclave/*` artifacts it looks like
|
||||
// "js @hexclave/next@1.0.0". Anything else (template build, source mode) is
|
||||
// a no-op.
|
||||
return /^js @stackframe\//.test(clientVersion);
|
||||
}
|
||||
|
||||
function warnOnce() {
|
||||
const g = globalThis as Record<symbol, unknown>;
|
||||
if (g[WARNED_SYMBOL]) return;
|
||||
g[WARNED_SYMBOL] = true;
|
||||
// Best-effort advisory. Wrapped in try/catch because some sandboxed
|
||||
// runtimes (older Workers, embedded JS hosts) stub or omit `console`;
|
||||
// a throw here would break the consuming bundle on SDK import.
|
||||
try {
|
||||
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`[Hexclave] You are using the legacy ${extractPackageName()} package. ` +
|
||||
`Please migrate to the @hexclave/* equivalent — the API surface is identical ` +
|
||||
`and the @stackframe/* packages are deprecated. See https://docs.hexclave.com/migration.`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// swallow: the warning is best-effort, never load-bearing.
|
||||
}
|
||||
}
|
||||
|
||||
function extractPackageName(): string {
|
||||
// "js @stackframe/stack@2.8.92" → "@stackframe/stack"
|
||||
const match = clientVersion.match(/^js (@stackframe\/[^@]+)@/);
|
||||
return match ? match[1] : "@stackframe/*";
|
||||
}
|
||||
|
||||
if (shouldWarn()) {
|
||||
warnOnce();
|
||||
}
|
||||
@ -1,217 +0,0 @@
|
||||
/**
|
||||
* 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/js@2.8.105`) and we need
|
||||
// to bump both halves in lockstep. If the name sweep ran first it
|
||||
// would rewrite just the name half (→ `js @hexclave/js@2.8.105`),
|
||||
// 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();
|
||||
Loading…
Reference in New Issue
Block a user