@hexclave/shared 1.0.22 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/ai/unified-prompts/reminders.js +2 -1
  2. package/dist/ai/unified-prompts/reminders.js.map +1 -1
  3. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
  4. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +15 -0
  5. package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  6. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +1 -46
  7. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +1 -62
  8. package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
  9. package/dist/config/schema.d.ts +80 -80
  10. package/dist/esm/ai/unified-prompts/reminders.js +2 -1
  11. package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
  12. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
  13. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +15 -0
  14. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
  15. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +1 -46
  16. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +1 -62
  17. package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
  18. package/dist/esm/config/schema.d.ts +80 -80
  19. package/dist/esm/interface/admin-interface.js +1 -1
  20. package/dist/esm/interface/admin-metrics.d.ts +13 -13
  21. package/dist/esm/interface/client-interface.js +1 -1
  22. package/dist/esm/interface/conversations.d.ts +35 -35
  23. package/dist/esm/interface/crud/current-user.d.ts +10 -10
  24. package/dist/esm/interface/crud/email-outbox.d.ts +116 -116
  25. package/dist/esm/interface/crud/invoices.d.ts +2 -2
  26. package/dist/esm/interface/crud/products.d.ts +15 -15
  27. package/dist/esm/interface/crud/products.d.ts.map +1 -1
  28. package/dist/esm/interface/crud/project-api-keys.d.ts +2 -2
  29. package/dist/esm/interface/crud/projects.d.ts +36 -36
  30. package/dist/esm/interface/crud/team-member-profiles.d.ts +36 -36
  31. package/dist/esm/interface/crud/transactions.d.ts +21 -21
  32. package/dist/esm/interface/crud/transactions.d.ts.map +1 -1
  33. package/dist/esm/interface/crud/users.d.ts +18 -18
  34. package/dist/esm/interface/server-interface.js +1 -1
  35. package/dist/esm/interface/webhooks.d.ts +2 -2
  36. package/dist/esm/known-errors.d.ts +5 -5
  37. package/dist/esm/schema-fields.d.ts +5 -5
  38. package/dist/esm/sessions.d.ts +7 -7
  39. package/dist/esm/utils/env.d.ts +2 -1
  40. package/dist/esm/utils/env.d.ts.map +1 -1
  41. package/dist/esm/utils/env.js +29 -17
  42. package/dist/esm/utils/env.js.map +1 -1
  43. package/dist/esm/utils/env.test.d.ts +1 -0
  44. package/dist/esm/utils/env.test.js +49 -0
  45. package/dist/esm/utils/env.test.js.map +1 -0
  46. package/dist/esm/utils/errors.d.ts.map +1 -1
  47. package/dist/esm/utils/errors.js +4 -1
  48. package/dist/esm/utils/errors.js.map +1 -1
  49. package/dist/interface/admin-interface.js +1 -1
  50. package/dist/interface/admin-metrics.d.ts +13 -13
  51. package/dist/interface/client-interface.js +1 -1
  52. package/dist/interface/conversations.d.ts +35 -35
  53. package/dist/interface/crud/current-user.d.ts +10 -10
  54. package/dist/interface/crud/email-outbox.d.ts +116 -116
  55. package/dist/interface/crud/invoices.d.ts +2 -2
  56. package/dist/interface/crud/products.d.ts +15 -15
  57. package/dist/interface/crud/products.d.ts.map +1 -1
  58. package/dist/interface/crud/project-api-keys.d.ts +2 -2
  59. package/dist/interface/crud/projects.d.ts +36 -36
  60. package/dist/interface/crud/team-member-profiles.d.ts +36 -36
  61. package/dist/interface/crud/transactions.d.ts +21 -21
  62. package/dist/interface/crud/transactions.d.ts.map +1 -1
  63. package/dist/interface/crud/users.d.ts +18 -18
  64. package/dist/interface/server-interface.js +1 -1
  65. package/dist/interface/webhooks.d.ts +2 -2
  66. package/dist/known-errors.d.ts +5 -5
  67. package/dist/schema-fields.d.ts +5 -5
  68. package/dist/sessions.d.ts +7 -7
  69. package/dist/utils/env.d.ts +2 -1
  70. package/dist/utils/env.d.ts.map +1 -1
  71. package/dist/utils/env.js +29 -16
  72. package/dist/utils/env.js.map +1 -1
  73. package/dist/utils/env.test.d.ts +1 -0
  74. package/dist/utils/env.test.js +49 -0
  75. package/dist/utils/env.test.js.map +1 -0
  76. package/dist/utils/errors.d.ts.map +1 -1
  77. package/dist/utils/errors.js +4 -1
  78. package/dist/utils/errors.js.map +1 -1
  79. package/package.json +1 -1
  80. package/src/ai/unified-prompts/reminders.ts +1 -0
  81. package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +15 -0
  82. package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +1 -62
  83. package/src/utils/env.test.tsx +58 -0
  84. package/src/utils/env.tsx +36 -20
  85. package/src/utils/errors.tsx +6 -1
@@ -1,6 +1,7 @@
1
1
  import { ALL_APPS, type AppId } from "../../../apps/apps-config";
2
2
  import { typedEntries } from "../../../utils/objects";
3
3
  import { deindent } from "../../../utils/strings";
4
+ import { remindersPrompt } from "../reminders";
4
5
 
5
6
  export const convexSetupPrompt = deindent`
6
7
  ## Convex Setup
@@ -593,6 +594,8 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
593
594
  Then wire the SDK setup above: create the Hexclave App object, wrap React apps in the provider, and add handler/auth pages where your framework needs them. OAuth client IDs/secrets and trusted domains are environment-specific, so leave placeholders or ask the user for those instead of inventing them. See [Auth providers](https://docs.hexclave.com/guides/apps/authentication/auth-providers) and [hexclave.config.ts: Auth](https://docs.hexclave.com/guides/going-further/hexclave-config#auth).
594
595
  `,
595
596
  "fraud-protection": deindent`
597
+ Key concepts: sign-up rules are ordered checks that decide whether a sign-up should be allowed, rejected, restricted, or logged; rule priority decides which matching rule wins when multiple rules apply.
598
+
596
599
  Start by writing the first sign-up rules in \`hexclave.config.ts\`. For a company-only product, default to reject and explicitly allow the company domain:
597
600
 
598
601
  \`\`\`ts title="hexclave.config.ts"
@@ -633,6 +636,8 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
633
636
  In the app, use team IDs in deep links wherever possible, then add a team switcher for navigation convenience. If team-specific authorization matters, configure RBAC next and enforce checks on the server. See [Teams](https://docs.hexclave.com/guides/apps/teams/overview), [Team Selection](https://docs.hexclave.com/guides/apps/teams/team-selection), and [hexclave.config.ts: Teams and Users](https://docs.hexclave.com/guides/going-further/hexclave-config#teams-and-users).
634
637
  `,
635
638
  "rbac": deindent`
639
+ Key concepts: permissions are stable IDs your app checks before protected actions; scopes decide whether a permission applies globally or within a team; contained permissions let one permission include other permissions recursively.
640
+
636
641
  Start with the permission IDs the product will check in code. For a basic team app, define reader/writer permissions and an admin-style composed permission:
637
642
 
638
643
  \`\`\`ts title="hexclave.config.ts"
@@ -663,6 +668,8 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
663
668
  Use \`scope: "project"\` only for global project-level actions. Client-side permission checks are UX only; always enforce the same permissions on the server. See [RBAC Permissions](https://docs.hexclave.com/guides/apps/rbac/overview) and [hexclave.config.ts: RBAC](https://docs.hexclave.com/guides/going-further/hexclave-config#rbac).
664
669
  `,
665
670
  "api-keys": deindent`
671
+ API keys allow you to programmatically create API keys for your own users. This is useful if you have an API yourself that you want to authenticate users against.
672
+
666
673
  Start by enabling only the owner types the product actually needs. For a platform with both personal and workspace APIs:
667
674
 
668
675
  \`\`\`ts title="hexclave.config.ts"
@@ -681,6 +688,8 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
681
688
  Then expose built-in account/team settings UI or build focused create/list/revoke screens. Always validate API keys on a trusted backend before serving protected requests. See [API Keys](https://docs.hexclave.com/guides/apps/api-keys/overview) and [hexclave.config.ts: API Keys](https://docs.hexclave.com/guides/going-further/hexclave-config#api-keys).
682
689
  `,
683
690
  "payments": deindent`
691
+ Key concepts: products are the sellable plans or one-time offers customers buy; product lines group mutually exclusive products such as pricing tiers; items are quantifiable entitlements such as credits, seats, or API calls granted by products; customers are the users, teams, or custom entities that own purchases and item balances.
692
+
684
693
  Start with a minimal catalog. For a user-plan SaaS with monthly credits:
685
694
 
686
695
  \`\`\`ts title="hexclave.config.ts"
@@ -691,6 +700,7 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
691
700
  },
692
701
  items: {
693
702
  credits: { displayName: "Credits", customerType: "user" },
703
+ access: { displayName: "Product Access", customerType: "user" },
694
704
  },
695
705
  products: {
696
706
  pro: {
@@ -702,6 +712,7 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
702
712
  },
703
713
  includedItems: {
704
714
  credits: { quantity: 1000, repeat: [1, "month"], expires: "when-repeated" },
715
+ access: { quantity: 1, expires: "when-purchase-expires" },
705
716
  },
706
717
  },
707
718
  },
@@ -714,6 +725,8 @@ const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
714
725
  Keep purchases in test mode while building; Stripe connection and \`payments.testMode\` are environment-specific, so configure them in the dashboard/environment rather than hard-coding secrets. In code, generate checkout URLs and read products/items to gate access. See [Payments: Getting started](https://docs.hexclave.com/guides/apps/payments/overview#getting-started), [Defining products](https://docs.hexclave.com/guides/apps/payments/overview#defining-products), and [Checking item balances](https://docs.hexclave.com/guides/apps/payments/overview#checking-item-balances).
715
726
  `,
716
727
  "emails": deindent`
728
+ Key concepts: templates define reusable email content with variables; themes provide shared branding around templates; transactional emails are required product emails, while marketing emails must respect opt-out expectations.
729
+
717
730
  Start with delivery: shared delivery is fine for development, but production should use Managed, Resend, or custom SMTP from **Emails -> Email Settings**. Delivery credentials and sender settings are environment-specific, so do not put secrets in \`hexclave.config.ts\`.
718
731
 
719
732
  Use config for versioned content. For example, add a product-specific template once you have the copy:
@@ -1272,6 +1285,8 @@ export const aiSetupPrompt = deindent`
1272
1285
 
1273
1286
  To use it, you can use the sections below to set up Hexclave in the project. For example, if you are setting up a Svelte project, you would follow the SDK setup instructions for a frontend JS project.
1274
1287
 
1288
+ ${remindersPrompt}
1289
+
1275
1290
  ${getSdkSetupPrompt("ai-prompt")}
1276
1291
 
1277
1292
  ${convexSetupPrompt}
@@ -255,68 +255,7 @@ const docsJson = {
255
255
  "/code-language-labels.js"
256
256
  ]
257
257
  },
258
- "redirects": [
259
- {
260
- "source": "/sdk/objects/stack-app",
261
- "destination": "/sdk/objects/hexclave-app"
262
- },
263
- {
264
- "source": "/sdk/hooks/use-stack-app",
265
- "destination": "/sdk/hooks/use-hexclave-app"
266
- },
267
- {
268
- "source": "/rest-api/overview",
269
- "destination": "/api/overview"
270
- },
271
- {
272
- "source": "/getting-started/setup",
273
- "destination": "/guides/getting-started/setup"
274
- },
275
- {
276
- "source": "/docs/getting-started/setup",
277
- "destination": "/guides/getting-started/setup"
278
- },
279
- {
280
- "source": "/docs/next/getting-started/setup",
281
- "destination": "/guides/getting-started/setup"
282
- },
283
- {
284
- "source": "/docs/sdk",
285
- "destination": "/sdk/overview"
286
- },
287
- {
288
- "source": "/docs/apps/analytics",
289
- "destination": "/guides/apps/analytics/overview"
290
- },
291
- {
292
- "source": "/docs/apps/api-keys",
293
- "destination": "/guides/apps/api-keys/overview"
294
- },
295
- {
296
- "source": "/docs/others/convex",
297
- "destination": "/guides/integrations/convex/overview"
298
- },
299
- {
300
- "source": "/docs/concepts/teams",
301
- "destination": "/guides/apps/teams/overview"
302
- },
303
- {
304
- "source": "/docs/concepts/custom-user-data",
305
- "destination": "/guides/getting-started/user-fundamentals#custom-metadata"
306
- },
307
- {
308
- "source": "/guides/going-further/user-metadata",
309
- "destination": "/guides/getting-started/user-fundamentals#custom-metadata"
310
- },
311
- {
312
- "source": "/others/js-client",
313
- "destination": "/sdk/objects/hexclave-app"
314
- },
315
- {
316
- "source": "/guides/going-further/stack-app",
317
- "destination": "/sdk/objects/hexclave-app"
318
- }
319
- ]
258
+ "redirects": []
320
259
  } as const;
321
260
 
322
261
  export default docsJson;
@@ -0,0 +1,58 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { getEnvVariable, getProcessEnv, resolveHexclaveStackEnvVarValue } from "./env";
3
+
4
+ afterEach(() => {
5
+ vi.unstubAllEnvs();
6
+ });
7
+
8
+ describe("Hexclave/Stack env var dual-read", () => {
9
+ it("falls back to the legacy Stack name when the Hexclave value is empty", () => {
10
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_API_URL", "");
11
+ vi.stubEnv("NEXT_PUBLIC_STACK_API_URL", "https://stack.example.test");
12
+
13
+ expect(getEnvVariable("NEXT_PUBLIC_STACK_API_URL")).toBe("https://stack.example.test");
14
+ expect(getProcessEnv("NEXT_PUBLIC_STACK_API_URL")).toBe("https://stack.example.test");
15
+ });
16
+
17
+ it("allows both names when they have the same non-empty value", () => {
18
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_API_URL", "https://api.example.test");
19
+ vi.stubEnv("NEXT_PUBLIC_STACK_API_URL", "https://api.example.test");
20
+
21
+ expect(getEnvVariable("NEXT_PUBLIC_STACK_API_URL")).toBe("https://api.example.test");
22
+ expect(getProcessEnv("NEXT_PUBLIC_STACK_API_URL")).toBe("https://api.example.test");
23
+ });
24
+
25
+ it("throws when both names are non-empty and different", () => {
26
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_API_URL", "https://hexclave.example.test");
27
+ vi.stubEnv("NEXT_PUBLIC_STACK_API_URL", "https://stack.example.test");
28
+
29
+ expect(() => getEnvVariable("NEXT_PUBLIC_STACK_API_URL")).toThrow(/NEXT_PUBLIC_HEXCLAVE_API_URL.*NEXT_PUBLIC_STACK_API_URL.*different values/);
30
+ expect(() => getProcessEnv("NEXT_PUBLIC_STACK_API_URL")).toThrow(/NEXT_PUBLIC_HEXCLAVE_API_URL.*NEXT_PUBLIC_STACK_API_URL.*different values/);
31
+ });
32
+
33
+ it("checks renamed legacy aliases when falling back", () => {
34
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_URL", "https://hexclave-url.example.test");
35
+ vi.stubEnv("NEXT_PUBLIC_STACK_URL", "https://stack-url.example.test");
36
+
37
+ expect(() => getEnvVariable("NEXT_PUBLIC_STACK_API_URL")).toThrow(/NEXT_PUBLIC_HEXCLAVE_URL.*NEXT_PUBLIC_STACK_URL.*different values/);
38
+ });
39
+
40
+ it("returns undefined when both names are empty", () => {
41
+ expect(resolveHexclaveStackEnvVarValue("HEXCLAVE_FOO", "STACK_FOO", "", "")).toBeUndefined();
42
+ });
43
+
44
+ it("falls back to the legacy Stack name when a canonical Hexclave name is looked up", () => {
45
+ vi.stubEnv("NEXT_PUBLIC_STACK_API_URL", "https://stack.example.test");
46
+
47
+ // Caller passes the canonical HEXCLAVE_ name but only the legacy value is set.
48
+ expect(getEnvVariable("NEXT_PUBLIC_HEXCLAVE_API_URL")).toBe("https://stack.example.test");
49
+ expect(getProcessEnv("NEXT_PUBLIC_HEXCLAVE_API_URL")).toBe("https://stack.example.test");
50
+ });
51
+
52
+ it("throws on a conflict when a canonical Hexclave name is looked up", () => {
53
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_API_URL", "https://hexclave.example.test");
54
+ vi.stubEnv("NEXT_PUBLIC_STACK_API_URL", "https://stack.example.test");
55
+
56
+ expect(() => getEnvVariable("NEXT_PUBLIC_HEXCLAVE_API_URL")).toThrow(/NEXT_PUBLIC_HEXCLAVE_API_URL.*NEXT_PUBLIC_STACK_API_URL.*different values/);
57
+ });
58
+ });
package/src/utils/env.tsx CHANGED
@@ -6,22 +6,37 @@ export function isBrowserLike() {
6
6
  }
7
7
 
8
8
  // newName: oldName
9
- const ENV_VAR_RENAME: Record<string, string[]> = {
9
+ const ENV_VAR_RENAME: Record<string, string[] | undefined> = {
10
10
  NEXT_PUBLIC_STACK_API_URL: ['STACK_BASE_URL', 'NEXT_PUBLIC_STACK_URL'],
11
11
  };
12
12
 
13
+ export function resolveHexclaveStackEnvVarValue(hexclaveName: string, stackName: string, hexclaveValue: string | undefined, stackValue: string | undefined): string | undefined {
14
+ if (hexclaveValue && stackValue && hexclaveValue !== stackValue) {
15
+ throw new Error(`Environment variables ${hexclaveName} and ${stackName} are both set to different values. Remove one of them or set them to the same value.`);
16
+ }
17
+ return hexclaveValue || stackValue || undefined;
18
+ }
19
+
13
20
  /**
14
- * Hexclave rebrand: compute the `HEXCLAVE_*`-prefixed equivalent of a `STACK_*`
15
- * env var name by replacing the first `STACK_` occurrence with `HEXCLAVE_`.
16
- * Covers `STACK_FOO`, `NEXT_PUBLIC_STACK_FOO`, `NEXT_PUBLIC_BROWSER_STACK_FOO`,
17
- * `NEXT_PUBLIC_SERVER_STACK_FOO`, `VITE_STACK_FOO`. Returns `undefined` when the
18
- * name has no `STACK_` segment (caller should behave exactly as before).
21
+ * Hexclave rebrand: resolve an env var by reading both the `HEXCLAVE_*` and
22
+ * `STACK_*` spellings, preferring the canonical Hexclave value and falling back
23
+ * to the legacy Stack value (empty counts as unset). Works in BOTH directions —
24
+ * whether the caller passes the legacy `STACK_FOO` name or the canonical
25
+ * `HEXCLAVE_FOO` name, the other spelling is still honored. Covers `STACK_FOO`,
26
+ * `NEXT_PUBLIC_STACK_FOO`, `NEXT_PUBLIC_BROWSER_STACK_FOO`,
27
+ * `NEXT_PUBLIC_SERVER_STACK_FOO`, `VITE_STACK_FOO` and their HEXCLAVE_ twins.
28
+ * Names with neither segment behave exactly as before.
19
29
  */
20
- function getHexclaveEnvVarName(name: string): string | undefined {
21
- if (!name.includes("STACK_")) {
22
- return undefined;
30
+ function getEnvVarWithHexclaveFallback(name: string): string | undefined {
31
+ if (name.includes("STACK_")) {
32
+ const hexclaveName = name.replace("STACK_", "HEXCLAVE_");
33
+ return resolveHexclaveStackEnvVarValue(hexclaveName, name, process.env[hexclaveName], process.env[name]);
34
+ }
35
+ if (name.includes("HEXCLAVE_")) {
36
+ const stackName = name.replace("HEXCLAVE_", "STACK_");
37
+ return resolveHexclaveStackEnvVarValue(name, stackName, process.env[name], process.env[stackName]);
23
38
  }
24
- return name.replace("STACK_", "HEXCLAVE_");
39
+ return process.env[name];
25
40
  }
26
41
 
27
42
  /**
@@ -45,21 +60,21 @@ export function getEnvVariable(name: string, defaultValue?: string | undefined):
45
60
 
46
61
  // throw error if the old name is used as the retrieve key
47
62
  for (const [newName, oldNames] of Object.entries(ENV_VAR_RENAME)) {
48
- if (oldNames.includes(name)) {
63
+ if (oldNames?.includes(name)) {
49
64
  throwErr(`Environment variable ${name} has been renamed to ${newName}. Please update your configuration to use the new name.`);
50
65
  }
51
66
  }
52
67
 
53
68
  // Hexclave rebrand: prefer the HEXCLAVE_*-prefixed equivalent, fall back to the STACK_* name.
54
- const hexclaveName = getHexclaveEnvVarName(name);
55
- let value = (hexclaveName ? process.env[hexclaveName] : undefined) ?? process.env[name];
69
+ // Treat the empty string as unset — the checked-in .env templates define empty
70
+ // HEXCLAVE_* placeholders, which must not shadow a real value under the legacy name.
71
+ let value = getEnvVarWithHexclaveFallback(name);
56
72
 
57
73
  // check the key under the old name if the new name is not found
58
- if (!value && ENV_VAR_RENAME[name] as any) {
59
- for (const oldName of ENV_VAR_RENAME[name]) {
60
- // Hexclave rebrand: also accept the HEXCLAVE_*-prefixed equivalent of each old alias.
61
- const hexclaveOldName = getHexclaveEnvVarName(oldName);
62
- value = (hexclaveOldName ? process.env[hexclaveOldName] : undefined) ?? process.env[oldName];
74
+ const renamedNames = ENV_VAR_RENAME[name];
75
+ if (!value && renamedNames != null) {
76
+ for (const oldName of renamedNames) {
77
+ value = getEnvVarWithHexclaveFallback(oldName);
63
78
  if (value) break;
64
79
  }
65
80
  }
@@ -112,6 +127,7 @@ export function getProcessEnv(name: string): string | undefined {
112
127
  return undefined;
113
128
  }
114
129
  // Hexclave rebrand: prefer the HEXCLAVE_*-prefixed equivalent, fall back to the STACK_* name.
115
- const hexclaveName = getHexclaveEnvVarName(name);
116
- return (hexclaveName ? process.env[hexclaveName] : undefined) ?? process.env[name];
130
+ // Empty counts as unset — the checked-in .env templates define empty HEXCLAVE_* placeholders,
131
+ // which must not shadow a real value under the legacy name.
132
+ return getEnvVarWithHexclaveFallback(name);
117
133
  }
@@ -80,7 +80,12 @@ export class HexclaveAssertionError extends Error {
80
80
 
81
81
  // Use literal dot-form (guarded with `typeof process`) so Next.js / webpack
82
82
  // DefinePlugin can inline the value at build time. See getProcessEnv in ./env.
83
- if ((typeof process !== "undefined" ? process.env.NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR : undefined) === "true") {
83
+ const hexclaveDebuggerValue = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_HEXCLAVE_DEBUGGER_ON_ASSERTION_ERROR : undefined;
84
+ const stackDebuggerValue = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR : undefined;
85
+ if (hexclaveDebuggerValue && stackDebuggerValue && hexclaveDebuggerValue !== stackDebuggerValue) {
86
+ throw new Error("Environment variables NEXT_PUBLIC_HEXCLAVE_DEBUGGER_ON_ASSERTION_ERROR and NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR are both set to different values. Remove one of them or set them to the same value.");
87
+ }
88
+ if ((hexclaveDebuggerValue || stackDebuggerValue) === "true") {
84
89
  debugger;
85
90
  }
86
91
  }