fix(rde): address review feedback on config load + import detection

- config-file: capture the raw jiti error for diagnostics instead of
  attaching it as `cause`. The dashboard error formatter (errorToNiceString
  -> nicify) renders `Error.cause` recursively, which would leak the
  underlying framework stack back into the user-facing message.
- config-rendering: add `@hexclave/tanstack-start` to CONFIG_IMPORT_PACKAGES
  so projects that only depend on it render `@hexclave/tanstack-start/config`
  instead of falling back to `@hexclave/js/config` (a missing-module TS error).
This commit is contained in:
Bilal Godil 2026-06-04 16:41:41 -07:00
parent 6a01e2507a
commit d748f38b3a
2 changed files with 7 additions and 1 deletions

View File

@ -3,6 +3,7 @@ import "server-only";
import { showOnboardingHexclaveConfigValue } from "@hexclave/shared/dist/config-authoring";
import { Config, isValidConfig } from "@hexclave/shared/dist/config/format";
import { detectImportPackageFromDir, renderConfigFileContent } from "@hexclave/shared/dist/config-rendering";
import { captureError } from "@hexclave/shared/dist/utils/errors";
import { createHash } from "crypto";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
import { createJiti } from "jiti";
@ -66,9 +67,12 @@ export async function readConfigFile(configFilePath: string): Promise<{ config:
try {
configModule = await jiti.import<unknown>(configFilePath);
} catch (error) {
// Capture the raw jiti/framework error for diagnostics, but don't attach it as `cause` on the thrown error:
// the dashboard's error formatter (errorToNiceString -> nicify) renders `Error.cause` recursively, which would
// leak the underlying framework stack/internals back into the user-facing message we're deliberately replacing.
captureError("remote-development-environment/readConfigFile", error);
throw new Error(
`Failed to load config file ${configFilePath}. If your config imports a value (e.g. defineHexclaveConfig) from a framework package such as "@hexclave/next", import it from that package's lightweight "/config" entrypoint instead, which doesn't load the framework runtime:\n\n import { defineHexclaveConfig } from "@hexclave/next/config";\n`,
{ cause: error },
);
}
if (!isConfigModule(configModule)) {

View File

@ -13,6 +13,7 @@ export { parseHexclaveConfigFileContent, renderConfigFileContent };
const CONFIG_IMPORT_PACKAGES = [
"@hexclave/next",
"@hexclave/react",
"@hexclave/tanstack-start",
"@hexclave/js",
"@hexclave/template",
"@stackframe/stack",
@ -139,6 +140,7 @@ import.meta.vitest?.test("detectConfigImportPackage picks first matching package
expect(detectConfigImportPackage(["@hexclave/next", "@hexclave/js"])).toBe("@hexclave/next");
expect(detectConfigImportPackage(["@hexclave/react", "@hexclave/js"])).toBe("@hexclave/react");
expect(detectConfigImportPackage(["@hexclave/js"])).toBe("@hexclave/js");
expect(detectConfigImportPackage(["@hexclave/tanstack-start"])).toBe("@hexclave/tanstack-start");
// Hexclave names take priority over legacy stackframe names when both appear.
expect(detectConfigImportPackage(["@stackframe/stack", "@hexclave/next"])).toBe("@hexclave/next");
// Legacy fallback still works for projects pinned to the last @stackframe/* release.