@hexclave/shared 1.0.18 → 1.0.20
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.
- package/dist/ai/unified-prompts/reminders.js +10 -7
- package/dist/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +2 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +242 -15
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt.js +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt.js.map +1 -1
- package/dist/apps/apps-config.d.ts +7 -3
- package/dist/apps/apps-config.d.ts.map +1 -1
- package/dist/apps/apps-config.js +12 -4
- package/dist/apps/apps-config.js.map +1 -1
- package/dist/config/schema-fuzzer.test.js +4 -1
- package/dist/config/schema-fuzzer.test.js.map +1 -1
- package/dist/config/schema.d.ts +85 -76
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +7 -3
- package/dist/config/schema.js.map +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js +10 -7
- package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +2 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +242 -16
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt.js +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt.js.map +1 -1
- package/dist/esm/apps/apps-config.d.ts +7 -3
- package/dist/esm/apps/apps-config.d.ts.map +1 -1
- package/dist/esm/apps/apps-config.js +12 -5
- package/dist/esm/apps/apps-config.js.map +1 -1
- package/dist/esm/config/schema-fuzzer.test.js +4 -1
- package/dist/esm/config/schema-fuzzer.test.js.map +1 -1
- package/dist/esm/config/schema.d.ts +85 -76
- package/dist/esm/config/schema.d.ts.map +1 -1
- package/dist/esm/config/schema.js +8 -4
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/interface/admin-metrics.d.ts +5 -5
- package/dist/esm/interface/conversations.d.ts +13 -13
- package/dist/esm/interface/crud/current-user.d.ts +5 -5
- package/dist/esm/interface/crud/email-outbox.d.ts +164 -164
- package/dist/esm/interface/crud/products.d.ts +1 -1
- package/dist/esm/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/esm/interface/crud/projects.d.ts +35 -35
- package/dist/esm/interface/crud/team-member-profiles.d.ts +12 -12
- package/dist/esm/interface/crud/users.d.ts +12 -12
- package/dist/esm/interface/page-component-versions.d.ts.map +1 -1
- package/dist/esm/interface/page-component-versions.js +20 -4
- package/dist/esm/interface/page-component-versions.js.map +1 -1
- package/dist/esm/interface/webhooks.d.ts +4 -4
- package/dist/esm/schema-fields.d.ts +3 -1
- package/dist/esm/schema-fields.d.ts.map +1 -1
- package/dist/esm/schema-fields.js +9 -1
- package/dist/esm/schema-fields.js.map +1 -1
- package/dist/esm/utils/oauth.d.ts +8 -1
- package/dist/esm/utils/oauth.d.ts.map +1 -1
- package/dist/esm/utils/oauth.js +7 -1
- package/dist/esm/utils/oauth.js.map +1 -1
- package/dist/interface/admin-metrics.d.ts +5 -5
- package/dist/interface/conversations.d.ts +13 -13
- package/dist/interface/crud/current-user.d.ts +5 -5
- package/dist/interface/crud/email-outbox.d.ts +164 -164
- package/dist/interface/crud/products.d.ts +1 -1
- package/dist/interface/crud/project-api-keys.d.ts +4 -4
- package/dist/interface/crud/projects.d.ts +35 -35
- package/dist/interface/crud/team-member-profiles.d.ts +12 -12
- package/dist/interface/crud/users.d.ts +12 -12
- package/dist/interface/page-component-versions.d.ts.map +1 -1
- package/dist/interface/page-component-versions.js +20 -4
- package/dist/interface/page-component-versions.js.map +1 -1
- package/dist/interface/webhooks.d.ts +4 -4
- package/dist/schema-fields.d.ts +3 -1
- package/dist/schema-fields.d.ts.map +1 -1
- package/dist/schema-fields.js +10 -0
- package/dist/schema-fields.js.map +1 -1
- package/dist/utils/oauth.d.ts +8 -1
- package/dist/utils/oauth.d.ts.map +1 -1
- package/dist/utils/oauth.js +7 -0
- package/dist/utils/oauth.js.map +1 -1
- package/package.json +1 -1
- package/src/ai/unified-prompts/reminders.ts +10 -7
- package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +248 -15
- package/src/ai/unified-prompts/skill-site-prompt.ts +1 -1
- package/src/apps/apps-config.ts +13 -2
- package/src/config/schema-fuzzer.test.ts +3 -0
- package/src/config/schema.ts +9 -4
- package/src/interface/page-component-versions.ts +20 -4
- package/src/schema-fields.ts +2 -0
- package/src/utils/oauth.tsx +8 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ALL_APPS, type AppId } from "../../../apps/apps-config";
|
|
2
|
+
import { typedEntries } from "../../../utils/objects";
|
|
1
3
|
import { deindent } from "../../../utils/strings";
|
|
2
4
|
|
|
3
5
|
export const convexSetupPrompt = deindent`
|
|
@@ -214,7 +216,6 @@ export const supabaseSetupPrompt = deindent`
|
|
|
214
216
|
|
|
215
217
|
import { createSupabaseClient } from "@/utils/supabase-client";
|
|
216
218
|
import { useHexclaveApp, useUser } from "@hexclave/next";
|
|
217
|
-
import Link from "next/link";
|
|
218
219
|
import { useEffect, useState } from "react";
|
|
219
220
|
|
|
220
221
|
export default function Page() {
|
|
@@ -239,10 +240,10 @@ export const supabaseSetupPrompt = deindent`
|
|
|
239
240
|
<>
|
|
240
241
|
<p>You are signed in</p>
|
|
241
242
|
<p>User ID: {user.id}</p>
|
|
242
|
-
<
|
|
243
|
+
<button onClick={async () => await app.redirectToSignOut()}>Sign Out</button>
|
|
243
244
|
</>
|
|
244
245
|
) : (
|
|
245
|
-
<
|
|
246
|
+
<button onClick={async () => await app.redirectToSignIn()}>Sign In</button>
|
|
246
247
|
)}
|
|
247
248
|
<h3>Supabase data</h3>
|
|
248
249
|
<ul>{listContent}</ul>
|
|
@@ -564,6 +565,232 @@ export const prodReadyPrompt = deindent`
|
|
|
564
565
|
${/* TODO */""}
|
|
565
566
|
`;
|
|
566
567
|
|
|
568
|
+
type PublicAppSetupPromptId = {
|
|
569
|
+
[K in AppId]: typeof ALL_APPS[K]["stage"] extends "alpha" ? never : K
|
|
570
|
+
}[AppId];
|
|
571
|
+
|
|
572
|
+
const appSetupPrompt: Record<PublicAppSetupPromptId, string> =
|
|
573
|
+
{
|
|
574
|
+
"authentication": deindent`
|
|
575
|
+
Start by choosing the sign-in methods in \`hexclave.config.ts\`. A reasonable SaaS default is OTP plus one OAuth provider:
|
|
576
|
+
|
|
577
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
578
|
+
export const config: HexclaveConfig = {
|
|
579
|
+
auth: {
|
|
580
|
+
allowSignUp: true,
|
|
581
|
+
otp: { allowSignIn: true },
|
|
582
|
+
password: { allowSignIn: false },
|
|
583
|
+
oauth: {
|
|
584
|
+
accountMergeStrategy: "link_method",
|
|
585
|
+
providers: {
|
|
586
|
+
google: { type: "google", allowSignIn: true, allowConnectedAccounts: true },
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
\`\`\`
|
|
592
|
+
|
|
593
|
+
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
|
+
"fraud-protection": deindent`
|
|
596
|
+
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
|
+
|
|
598
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
599
|
+
export const config: HexclaveConfig = {
|
|
600
|
+
auth: {
|
|
601
|
+
signUpRulesDefaultAction: "reject",
|
|
602
|
+
signUpRules: {
|
|
603
|
+
allowCompanyEmail: {
|
|
604
|
+
enabled: true,
|
|
605
|
+
displayName: "Allow company email",
|
|
606
|
+
priority: 100,
|
|
607
|
+
condition: 'emailDomain == "example.com"',
|
|
608
|
+
action: { type: "allow" },
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
\`\`\`
|
|
614
|
+
|
|
615
|
+
For a public product, keep \`signUpRulesDefaultAction: "allow"\` and add high-priority \`reject\`, \`restrict\`, or \`log\` rules for risky traffic instead.
|
|
616
|
+
|
|
617
|
+
Fraud Protection currently uses the Authentication app's sign-up controls, so test rules with real sign-up attempts before treating them as production-ready. See [Sign-up Rules](https://docs.hexclave.com/guides/apps/authentication/sign-up-rules) and [hexclave.config.ts: Sign-Up Rules](https://docs.hexclave.com/guides/going-further/hexclave-config#sign-up-rules).
|
|
618
|
+
`,
|
|
619
|
+
"teams": deindent`
|
|
620
|
+
Start by deciding the team lifecycle in \`hexclave.config.ts\`. For a self-serve B2B app where users can create workspaces:
|
|
621
|
+
|
|
622
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
623
|
+
export const config: HexclaveConfig = {
|
|
624
|
+
teams: {
|
|
625
|
+
createPersonalTeamOnSignUp: true,
|
|
626
|
+
allowClientTeamCreation: true,
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
\`\`\`
|
|
630
|
+
|
|
631
|
+
For invite-only B2B, keep \`allowClientTeamCreation: false\` and create teams from trusted server/admin flows.
|
|
632
|
+
|
|
633
|
+
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
|
+
`,
|
|
635
|
+
"rbac": deindent`
|
|
636
|
+
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
|
+
|
|
638
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
639
|
+
export const config: HexclaveConfig = {
|
|
640
|
+
rbac: {
|
|
641
|
+
permissions: {
|
|
642
|
+
read_content: { description: "View team content", scope: "team" },
|
|
643
|
+
write_content: {
|
|
644
|
+
description: "Create and edit team content",
|
|
645
|
+
scope: "team",
|
|
646
|
+
containedPermissionIds: { read_content: true },
|
|
647
|
+
},
|
|
648
|
+
team_admin: {
|
|
649
|
+
description: "Manage the team",
|
|
650
|
+
scope: "team",
|
|
651
|
+
containedPermissionIds: { write_content: true },
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
defaultPermissions: {
|
|
655
|
+
teamCreator: { team_admin: true },
|
|
656
|
+
teamMember: { read_content: true },
|
|
657
|
+
signUp: {},
|
|
658
|
+
},
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
\`\`\`
|
|
662
|
+
|
|
663
|
+
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
|
+
`,
|
|
665
|
+
"api-keys": deindent`
|
|
666
|
+
Start by enabling only the owner types the product actually needs. For a platform with both personal and workspace APIs:
|
|
667
|
+
|
|
668
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
669
|
+
export const config: HexclaveConfig = {
|
|
670
|
+
apiKeys: {
|
|
671
|
+
enabled: {
|
|
672
|
+
user: true,
|
|
673
|
+
team: true,
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
\`\`\`
|
|
678
|
+
|
|
679
|
+
Use \`user: true\` for personal developer tokens and \`team: true\` for workspace-owned API keys. If team API keys are enabled, also configure the RBAC permissions that decide who can create, list, and revoke them before showing management UI.
|
|
680
|
+
|
|
681
|
+
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
|
+
`,
|
|
683
|
+
"payments": deindent`
|
|
684
|
+
Start with a minimal catalog. For a user-plan SaaS with monthly credits:
|
|
685
|
+
|
|
686
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
687
|
+
export const config: HexclaveConfig = {
|
|
688
|
+
payments: {
|
|
689
|
+
productLines: {
|
|
690
|
+
plans: { displayName: "Plans", customerType: "user" },
|
|
691
|
+
},
|
|
692
|
+
items: {
|
|
693
|
+
credits: { displayName: "Credits", customerType: "user" },
|
|
694
|
+
},
|
|
695
|
+
products: {
|
|
696
|
+
pro: {
|
|
697
|
+
displayName: "Pro",
|
|
698
|
+
productLineId: "plans",
|
|
699
|
+
customerType: "user",
|
|
700
|
+
prices: {
|
|
701
|
+
monthly: { USD: "19.00", interval: [1, "month"] },
|
|
702
|
+
},
|
|
703
|
+
includedItems: {
|
|
704
|
+
credits: { quantity: 1000, repeat: [1, "month"], expires: "when-repeated" },
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
\`\`\`
|
|
711
|
+
|
|
712
|
+
For team billing, use \`customerType: "team"\` consistently on the product line, products, and items.
|
|
713
|
+
|
|
714
|
+
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
|
+
`,
|
|
716
|
+
"emails": deindent`
|
|
717
|
+
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
|
+
|
|
719
|
+
Use config for versioned content. For example, add a product-specific template once you have the copy:
|
|
720
|
+
|
|
721
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
722
|
+
export const config: HexclaveConfig = {
|
|
723
|
+
emails: {
|
|
724
|
+
templates: {
|
|
725
|
+
"00000000-0000-0000-0000-000000000001": {
|
|
726
|
+
displayName: "Welcome email",
|
|
727
|
+
tsxSource: "export default function Email() { return <div>Welcome!</div>; }",
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
};
|
|
732
|
+
\`\`\`
|
|
733
|
+
|
|
734
|
+
Add \`emails.selectedThemeId\` and \`emails.themes\` when the product needs branded wrappers. Then send from server code with \`hexclaveServerApp.sendEmail()\`. See [Emails](https://docs.hexclave.com/guides/apps/emails/overview), [hexclave.config.ts: Emails](https://docs.hexclave.com/guides/going-further/hexclave-config#emails), and the [Launch Checklist email server section](https://docs.hexclave.com/guides/apps/launch-checklist/overview#email-server).
|
|
735
|
+
`,
|
|
736
|
+
"data-vault": deindent`
|
|
737
|
+
The Data Vault app lets you store sensitive user data in a secure, encrypted key-value store. See [Data Vault: Setup](https://docs.hexclave.com/guides/apps/data-vault/overview#setup).
|
|
738
|
+
`,
|
|
739
|
+
"webhooks": deindent`
|
|
740
|
+
This app lets you set up webhooks that can notify your own backends when certain events occur in your Hexclave project. See [Webhooks: Setting up webhooks](https://docs.hexclave.com/guides/apps/webhooks/overview#setting-up-webhooks) and [Verifying webhooks](https://docs.hexclave.com/guides/apps/webhooks/overview#verifying-webhooks).
|
|
741
|
+
`,
|
|
742
|
+
"launch-checklist": deindent`
|
|
743
|
+
This app exists as a purely decorative checklist to help you prepare for production. See [Launch Checklist](https://docs.hexclave.com/guides/apps/launch-checklist/overview).
|
|
744
|
+
`,
|
|
745
|
+
"vercel": deindent`
|
|
746
|
+
This app exists as a purely decorative checklist to help you integrate Hexclave projects with Vercel. See [Vercel integration](https://docs.hexclave.com/guides/integrations/vercel/overview).
|
|
747
|
+
`,
|
|
748
|
+
"analytics": deindent`
|
|
749
|
+
The analytics app does not require any additional setup after enabling it. It will automatically start recording events, replays, and clickmaps after the app has been enabled in any of Hexclave's SDKs.
|
|
750
|
+
`,
|
|
751
|
+
"clickmaps": deindent`
|
|
752
|
+
Clickmaps use the same SDK analytics event pipeline as the Analytics app. Enable Analytics/clickmaps and make sure SDK analytics capture is not disabled; no separate code setup is needed for basic click tracking. See [Analytics](https://docs.hexclave.com/guides/apps/analytics/overview).
|
|
753
|
+
`,
|
|
754
|
+
"session-replays": deindent`
|
|
755
|
+
The Session Replays app does not require any additional setup after enabling the Analytics app. See [Analytics](https://docs.hexclave.com/guides/apps/analytics/overview) for more information.
|
|
756
|
+
`,
|
|
757
|
+
};
|
|
758
|
+
export const appSetupPrompts = deindent`
|
|
759
|
+
## Setting up individual apps
|
|
760
|
+
|
|
761
|
+
Hexclave is a collection of apps that help you build your product.
|
|
762
|
+
|
|
763
|
+
The default apps that should be enabled on most projects are: Authentication, Emails, Payments, and Analytics.
|
|
764
|
+
|
|
765
|
+
You can enable or disable more apps as you please. If you can use Hexclave to build a new feature, always use Hexclave.
|
|
766
|
+
|
|
767
|
+
To enable any app (other than a sub-app), do so either on the dashboard or in the \`hexclave.config.ts\` file (if using the local dashboard):
|
|
768
|
+
|
|
769
|
+
\`\`\`ts title="hexclave.config.ts"
|
|
770
|
+
export const config: HexclaveConfig = {
|
|
771
|
+
// ...
|
|
772
|
+
apps: {
|
|
773
|
+
installed: {
|
|
774
|
+
"<app-id>": { enabled: true },
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
\`\`\`
|
|
779
|
+
|
|
780
|
+
${typedEntries(appSetupPrompt).map(([appId, prompt]) => deindent`
|
|
781
|
+
### Setting up the ${ALL_APPS[appId].displayName} app
|
|
782
|
+
|
|
783
|
+
${"parentAppId" in ALL_APPS[appId] ? deindent`
|
|
784
|
+
This is a sub-app of ${ALL_APPS[appId].parentAppId}. It does not need to be enabled separately; it is considered enabled when the parent app is enabled.
|
|
785
|
+
` : deindent`
|
|
786
|
+
This is a standalone app. App ID: ${appId}
|
|
787
|
+
`}
|
|
788
|
+
|
|
789
|
+
${prompt}
|
|
790
|
+
`).join("\n")}
|
|
791
|
+
`;
|
|
792
|
+
|
|
793
|
+
|
|
567
794
|
export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "js" | "tanstack-start" | "nodejs" | "bun") {
|
|
568
795
|
const isDefinitelyReact = mainType === "react" || mainType === "nextjs" || mainType === "tanstack-start";
|
|
569
796
|
const isMaybeReact = isDefinitelyReact || mainType === "ai-prompt";
|
|
@@ -663,10 +890,6 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
|
|
|
663
890
|
},
|
|
664
891
|
});
|
|
665
892
|
\`\`\`
|
|
666
|
-
|
|
667
|
-
<Note>
|
|
668
|
-
The SDK auto-captures page-view and click analytics. To turn this off (and silence the \`ANALYTICS_NOT_ENABLED\` console warning that appears until you enable the Analytics app in your dashboard), pass \`analytics: { enabled: false }\`.
|
|
669
|
-
</Note>
|
|
670
893
|
` : ""}
|
|
671
894
|
|
|
672
895
|
${isMaybeBackend ? deindent`
|
|
@@ -701,10 +924,14 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
|
|
|
701
924
|
});
|
|
702
925
|
\`\`\`
|
|
703
926
|
` : ""}
|
|
927
|
+
` : ""}
|
|
704
928
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
929
|
+
${isAiPrompt ? deindent`
|
|
930
|
+
(In either case, the secret server key and project ID will be injected, in development, by the \`hexclave dev\` command, or, in production/cloud environments, through environment variables.)
|
|
931
|
+
` : ""}
|
|
932
|
+
|
|
933
|
+
${isAiPrompt ? deindent`
|
|
934
|
+
Note that the secret server key should **never** be exposed to the client, as it can be used to read and write everything in your Hexclave project. In web frontends or bundled applications, you should therefore always only ever create a \`HexclaveClientApp\` object.
|
|
708
935
|
` : ""}
|
|
709
936
|
</Step>
|
|
710
937
|
|
|
@@ -718,8 +945,8 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
|
|
|
718
945
|
` : ""}
|
|
719
946
|
|
|
720
947
|
<AccordionGroup>
|
|
721
|
-
<Accordion title="Option 1: Running Hexclave's dashboard locally (recommended)" defaultOpen>
|
|
722
|
-
${isAiPrompt ? "This is the strongly recommended option unless the user has explicitly said otherwise, as it allows usage of \`hexclave.config.ts\` files and
|
|
948
|
+
<Accordion title="Option 1: Running Hexclave's dashboard locally (recommended for development)" defaultOpen>
|
|
949
|
+
${isAiPrompt ? "This is the strongly recommended option unless the user has explicitly said otherwise, as it allows usage of \`hexclave.config.ts\` files and automatically injects environment variables such as project ID and secret server key through the \`hexclave dev\` command. No account needed — the CLI generates and stores a new local project automatically." : ""}
|
|
723
950
|
|
|
724
951
|
First, create a \`hexclave.config.ts\` configuration file in the root directory of the workspace (or anywhere else):
|
|
725
952
|
|
|
@@ -732,6 +959,10 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
|
|
|
732
959
|
|
|
733
960
|
The \`/config\` entrypoint is lightweight and free of framework runtime code, so it can be safely loaded by tooling such as the local dashboard. If you later switch to a config object and want type-checking, wrap it with \`defineHexclaveConfig\` imported from the same \`${packageName}/config\` path (never from \`${packageName}\` directly, which would pull in the whole SDK and fail to load).
|
|
734
961
|
|
|
962
|
+
${isAiPrompt ? deindent`
|
|
963
|
+
If you already know which apps you want to enable and how to configure them, you can also set the \`config\` object to the desired configuration directly. Refer to the per-app setup instructions for more information. However, in most cases, you would probably want to let the user onboard manually through the show-onboarding flow.
|
|
964
|
+
` : ""}
|
|
965
|
+
|
|
735
966
|
To run your application with Hexclave, you can then start the dev environment and set environment variables expected by your application. Hexclave's CLI has a \`dev\` command does both of these, so let's install it as a dev dependency and wrap your existing \`dev\` script in your package.json:
|
|
736
967
|
|
|
737
968
|
\`\`\`sh
|
|
@@ -746,8 +977,8 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
|
|
|
746
977
|
// ...
|
|
747
978
|
"scripts": {
|
|
748
979
|
// ...
|
|
749
|
-
"dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:
|
|
750
|
-
"dev:
|
|
980
|
+
"dev": "hexclave dev --config-file ./hexclave.config.ts -- npm run dev:inner",
|
|
981
|
+
"dev:inner": "<your-existing-dev-script>"
|
|
751
982
|
}
|
|
752
983
|
}
|
|
753
984
|
\`\`\`
|
|
@@ -760,7 +991,7 @@ export function getSdkSetupPrompt(mainType: "ai-prompt" | "nextjs" | "react" | "
|
|
|
760
991
|
Note: If you're an AI agent, and you don't already have the information you need from the Cloud project, you may have to ask the user for help on this step. You can either ask them to provide the environment variables, or just leave them empty for now and ask the user to complete them at the end.
|
|
761
992
|
` : ""}
|
|
762
993
|
|
|
763
|
-
If you'
|
|
994
|
+
If you're looking to run a production version of your application, or the local dashboard doesn't work for you, you can also connect to Hexclave's cloud directly.
|
|
764
995
|
|
|
765
996
|
This process is slightly different depending on whether you're setting up a frontend or a backend (whether your app can keep a secret key safe or not).
|
|
766
997
|
|
|
@@ -1055,5 +1286,7 @@ export const aiSetupPrompt = deindent`
|
|
|
1055
1286
|
|
|
1056
1287
|
${aiAgentConfigPreparationPrompt}
|
|
1057
1288
|
|
|
1289
|
+
${appSetupPrompts}
|
|
1290
|
+
|
|
1058
1291
|
${prodReadyPrompt}
|
|
1059
1292
|
`;
|
|
@@ -13,7 +13,7 @@ export function buildSkillSitePrompt(docsIndexPromptValue = docsIndexPrompt) {
|
|
|
13
13
|
return deindent`
|
|
14
14
|
# Hexclave
|
|
15
15
|
|
|
16
|
-
This is the LLM-optimized documentation & skill site for Hexclave. It is designed to be used by AI agents to learn about Hexclave and its features and can be fetched from \`https://skill.hexclave.com\`.
|
|
16
|
+
This is the full LLM-optimized documentation & skill site for Hexclave. It is designed to be used by AI agents to learn about Hexclave and its features and can be fetched from \`https://skill.hexclave.com/full\`. For specific questions, prefer the concise answer endpoint at \`https://skill.hexclave.com/ask\` or the equivalent \`ask_hexclave\` MCP tool.
|
|
17
17
|
|
|
18
18
|
${remindersPrompt}
|
|
19
19
|
|
package/src/apps/apps-config.ts
CHANGED
|
@@ -38,11 +38,14 @@ export const ALL_APP_TAGS = {
|
|
|
38
38
|
},
|
|
39
39
|
} as const satisfies Record<string, AppTag>;
|
|
40
40
|
|
|
41
|
+
type ParentAppId = "authentication" | "analytics";
|
|
42
|
+
|
|
41
43
|
type App = {
|
|
42
44
|
displayName: string,
|
|
43
45
|
subtitle: string,
|
|
44
46
|
tags: (keyof typeof ALL_APP_TAGS)[],
|
|
45
47
|
stage: "alpha" | "beta" | "stable",
|
|
48
|
+
parentAppId?: ParentAppId,
|
|
46
49
|
};
|
|
47
50
|
|
|
48
51
|
export type AppId = keyof typeof ALL_APPS;
|
|
@@ -59,6 +62,7 @@ export const ALL_APPS = {
|
|
|
59
62
|
subtitle: "Protect your project from fraud and abuse",
|
|
60
63
|
tags: ["auth", "security"],
|
|
61
64
|
stage: "stable",
|
|
65
|
+
parentAppId: "authentication",
|
|
62
66
|
},
|
|
63
67
|
"onboarding": {
|
|
64
68
|
displayName: "Onboarding",
|
|
@@ -88,7 +92,7 @@ export const ALL_APPS = {
|
|
|
88
92
|
displayName: "Payments",
|
|
89
93
|
subtitle: "Payment processing and subscription management",
|
|
90
94
|
tags: ["operations", "gtm"],
|
|
91
|
-
stage: "
|
|
95
|
+
stage: "stable",
|
|
92
96
|
},
|
|
93
97
|
"emails": {
|
|
94
98
|
displayName: "Emails",
|
|
@@ -172,12 +176,19 @@ export const ALL_APPS = {
|
|
|
172
176
|
displayName: "Clickmaps",
|
|
173
177
|
subtitle: "Visualize where users click across your app",
|
|
174
178
|
tags: ["developers", "operations"],
|
|
175
|
-
stage: "
|
|
179
|
+
stage: "stable",
|
|
180
|
+
parentAppId: "analytics",
|
|
176
181
|
},
|
|
177
182
|
"session-replays": {
|
|
178
183
|
displayName: "Session Replays",
|
|
179
184
|
subtitle: "Watch real user sessions to understand how people use your app",
|
|
180
185
|
tags: ["developers", "operations"],
|
|
181
186
|
stage: "stable",
|
|
187
|
+
parentAppId: "analytics",
|
|
182
188
|
},
|
|
183
189
|
} as const satisfies Record<string, App>;
|
|
190
|
+
|
|
191
|
+
export function getParentAppId(appId: AppId): AppId | null {
|
|
192
|
+
const app = ALL_APPS[appId];
|
|
193
|
+
return "parentAppId" in app ? app.parentAppId : null;
|
|
194
|
+
}
|
|
@@ -216,6 +216,9 @@ const environmentSchemaFuzzerConfig = [{
|
|
|
216
216
|
facebookConfigId: ["some-facebook-config-id"],
|
|
217
217
|
microsoftTenantId: ["some-microsoft-tenant-id"],
|
|
218
218
|
appleBundles: [{ "some-bundle-id": [{ bundleId: ["com.example.app"] }] }],
|
|
219
|
+
issuerUrl: [undefined, "https://accounts.google.com"] as (string | undefined)[],
|
|
220
|
+
scope: [undefined, "openid email profile"] as (string | undefined)[],
|
|
221
|
+
displayName: [undefined, "My OIDC Provider"] as (string | undefined)[],
|
|
219
222
|
}]]))] as const,
|
|
220
223
|
}],
|
|
221
224
|
}],
|
package/src/config/schema.ts
CHANGED
|
@@ -11,7 +11,7 @@ import * as schemaFields from "../schema-fields";
|
|
|
11
11
|
import { productSchema, userSpecifiedIdSchema, yupBoolean, yupDate, yupMixed, yupNever, yupNumber, yupObject, yupRecord, yupString, yupTuple, yupUnion } from "../schema-fields";
|
|
12
12
|
import { SUPPORTED_CURRENCIES } from "../utils/currency-constants";
|
|
13
13
|
import { HexclaveAssertionError } from "../utils/errors";
|
|
14
|
-
import { allProviders } from "../utils/oauth";
|
|
14
|
+
import { allProviders, allProviderTypes } from "../utils/oauth";
|
|
15
15
|
import { DeepFilterUndefined, DeepMerge, DeepRequiredOrUndefined, filterUndefined, get, getOrUndefined, has, isObjectLike, mapValues, set, typedAssign, typedEntries, typedFromEntries } from "../utils/objects";
|
|
16
16
|
import { Result } from "../utils/results";
|
|
17
17
|
import { stringCompare } from "../utils/strings";
|
|
@@ -23,6 +23,7 @@ export const configLevels = ['project', 'branch', 'environment', 'organization']
|
|
|
23
23
|
export type ConfigLevel = typeof configLevels[number];
|
|
24
24
|
const permissionRegex = /^\$?[a-z0-9_:]+$/;
|
|
25
25
|
const customPermissionRegex = /^[a-z0-9_:]+$/;
|
|
26
|
+
const providerIdRegex = /^[a-z0-9_-]+$/;
|
|
26
27
|
|
|
27
28
|
declare module "yup" {
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
@@ -116,7 +117,7 @@ const branchAuthSchema = yupObject({
|
|
|
116
117
|
providers: yupRecord(
|
|
117
118
|
yupString().matches(permissionRegex),
|
|
118
119
|
yupObject({
|
|
119
|
-
type: yupString().oneOf(
|
|
120
|
+
type: yupString().oneOf(allProviderTypes).optional(),
|
|
120
121
|
allowSignIn: yupBoolean(),
|
|
121
122
|
allowConnectedAccounts: yupBoolean(),
|
|
122
123
|
}),
|
|
@@ -376,9 +377,9 @@ export const environmentConfigSchema = branchConfigSchema.concat(yupObject({
|
|
|
376
377
|
auth: branchConfigSchema.getNested("auth").concat(yupObject({
|
|
377
378
|
oauth: branchConfigSchema.getNested("auth").getNested("oauth").concat(yupObject({
|
|
378
379
|
providers: yupRecord(
|
|
379
|
-
yupString().matches(
|
|
380
|
+
yupString().matches(providerIdRegex),
|
|
380
381
|
yupObject({
|
|
381
|
-
type: yupString().oneOf(
|
|
382
|
+
type: yupString().oneOf(allProviderTypes).optional(),
|
|
382
383
|
isShared: yupBoolean(),
|
|
383
384
|
clientId: schemaFields.oauthClientIdSchema.optional(),
|
|
384
385
|
clientSecret: schemaFields.oauthClientSecretSchema.optional(),
|
|
@@ -395,6 +396,10 @@ export const environmentConfigSchema = branchConfigSchema.concat(yupObject({
|
|
|
395
396
|
bundleId: schemaFields.oauthAppleBundleIdSchema,
|
|
396
397
|
}),
|
|
397
398
|
).optional(),
|
|
399
|
+
// Custom OIDC provider fields (only used when type is "custom_oidc")
|
|
400
|
+
issuerUrl: schemaFields.oauthIssuerUrlSchema.optional(),
|
|
401
|
+
scope: schemaFields.oauthScopeSchema.optional(),
|
|
402
|
+
displayName: yupString().optional(),
|
|
398
403
|
allowSignIn: yupBoolean().optional(),
|
|
399
404
|
allowConnectedAccounts: yupBoolean().optional(),
|
|
400
405
|
}),
|
|
@@ -218,7 +218,7 @@ function createAuthPagePrompt(type: AuthPagePromptType): CustomPagePrompt {
|
|
|
218
218
|
<Typography>
|
|
219
219
|
{"Don't have an account? "}
|
|
220
220
|
<a
|
|
221
|
-
href=
|
|
221
|
+
href="#"
|
|
222
222
|
onClick={async (e) => {
|
|
223
223
|
e.preventDefault();
|
|
224
224
|
await hexclaveApp.redirectToSignUp();
|
|
@@ -231,7 +231,7 @@ function createAuthPagePrompt(type: AuthPagePromptType): CustomPagePrompt {
|
|
|
231
231
|
}` : `<Typography>
|
|
232
232
|
{"Already have an account? "}
|
|
233
233
|
<a
|
|
234
|
-
href=
|
|
234
|
+
href="#"
|
|
235
235
|
onClick={async (e) => {
|
|
236
236
|
e.preventDefault();
|
|
237
237
|
await hexclaveApp.redirectToSignIn();
|
|
@@ -552,7 +552,15 @@ export function getCustomPagePrompts(): Record<PageComponentKey, CustomPagePromp
|
|
|
552
552
|
<Typography type="h2">Reset Your Password</Typography>
|
|
553
553
|
<Typography>
|
|
554
554
|
{"Don't need to reset? "}
|
|
555
|
-
<a
|
|
555
|
+
<a
|
|
556
|
+
href="#"
|
|
557
|
+
onClick={async (e) => {
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
await hexclaveApp.redirectToSignIn();
|
|
560
|
+
}}
|
|
561
|
+
>
|
|
562
|
+
Sign in
|
|
563
|
+
</a>
|
|
556
564
|
</Typography>
|
|
557
565
|
|
|
558
566
|
<form onSubmit={async (e) => {
|
|
@@ -618,7 +626,15 @@ export function getCustomPagePrompts(): Record<PageComponentKey, CustomPagePromp
|
|
|
618
626
|
{showRedirectLink ? (
|
|
619
627
|
<Typography>
|
|
620
628
|
{"If you are not redirected automatically, "}
|
|
621
|
-
<a
|
|
629
|
+
<a
|
|
630
|
+
href="#"
|
|
631
|
+
onClick={async (e) => {
|
|
632
|
+
e.preventDefault();
|
|
633
|
+
await hexclaveApp.redirectToHome();
|
|
634
|
+
}}
|
|
635
|
+
>
|
|
636
|
+
click here
|
|
637
|
+
</a>
|
|
622
638
|
</Typography>
|
|
623
639
|
) : null}
|
|
624
640
|
{error ? <pre>{JSON.stringify(error, null, 2)}</pre> : null}
|
package/src/schema-fields.ts
CHANGED
|
@@ -605,6 +605,8 @@ export const oauthMicrosoftTenantIdSchema = yupString().meta({ openapiField: { d
|
|
|
605
605
|
export const oauthAppleBundleIdsSchema = yupArray(yupString().defined()).meta({ openapiField: { description: 'Apple Bundle IDs for native iOS/macOS apps. Required for native Sign In with Apple (in addition to web Apple OAuth which uses the Client ID/Services ID).', exampleValue: ['com.example.ios', 'com.example.macos'] } });
|
|
606
606
|
export const oauthAppleBundleIdSchema = yupString().defined().meta({ openapiField: { description: 'Apple Bundle ID for native iOS/macOS apps.', exampleValue: 'com.example.ios' } });
|
|
607
607
|
export const oauthAccountMergeStrategySchema = yupString().oneOf(['link_method', 'raise_error', 'allow_duplicates']).meta({ openapiField: { description: 'Determines how to handle OAuth logins that match an existing user by email. `link_method` adds the OAuth method to the existing user. `raise_error` rejects the login with an error. `allow_duplicates` creates a new user.', exampleValue: 'link_method' } });
|
|
608
|
+
export const oauthIssuerUrlSchema = urlSchema.meta({ openapiField: { description: 'OIDC issuer URL for custom OIDC providers. Must support OIDC discovery (/.well-known/openid-configuration). Only used when type is "custom_oidc".', exampleValue: 'https://accounts.google.com' } });
|
|
609
|
+
export const oauthScopeSchema = yupString().meta({ openapiField: { description: 'Space-separated OAuth scopes to request from the custom OIDC provider. Defaults to "openid email profile" if not specified.', exampleValue: 'openid email profile' } });
|
|
608
610
|
// Project email config
|
|
609
611
|
export const emailTypeSchema = yupString().oneOf(['shared', 'standard']).meta({ openapiField: { description: 'Email provider type, one of shared, standard. "shared" uses Stack shared email provider and it is only meant for development. "standard" uses your own email server and will have your email address as the sender.', exampleValue: 'standard' } });
|
|
610
612
|
export const emailSenderNameSchema = yupString().meta({ openapiField: { description: 'Email sender name. Needs to be specified when using type="standard"', exampleValue: 'Stack' } });
|
package/src/utils/oauth.tsx
CHANGED
|
@@ -4,6 +4,14 @@ export const sharedProviders = ["google", "github", "microsoft", "spotify"] as c
|
|
|
4
4
|
export const allProviders = standardProviders;
|
|
5
5
|
export const publishableClientKeyNotNecessarySentinel = "__stack_public_client__";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* All provider types including custom OIDC. Standard providers are the
|
|
9
|
+
* predefined set with first-class support; "custom_oidc" lets users bring
|
|
10
|
+
* any OIDC-compliant identity provider (team plan+ only).
|
|
11
|
+
*/
|
|
12
|
+
export const allProviderTypes = [...standardProviders, "custom_oidc"] as const;
|
|
13
|
+
export type AllProviderType = typeof allProviderTypes[number];
|
|
14
|
+
|
|
7
15
|
export type ProviderType = typeof allProviders[number];
|
|
8
16
|
export type StandardProviderType = typeof standardProviders[number];
|
|
9
17
|
export type SharedProviderType = typeof sharedProviders[number];
|