mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
## Stack Auth → Hexclave rename — PR 5 (internal symbols, paths,
packages, brand strings)
PR 5 finishes the **internal / non-wire** half of the Stack→Hexclave
rename. It only touches things where nothing outside the repo depends on
the exact name: internal symbols, file/dir names, the
`@stackframe/template` package, and residual brand strings. Plan +
progress are in `HEXCLAVE-RENAME-PR5-PLAN.md`.
Every step was verified green (`pnpm typecheck` + `pnpm lint`, 28/28)
and committed as its own checkpoint, then a fan-out of review agents
audited all commits and the findings were fixed.
### What changed
- **Internal symbols** (`@hexclave/shared`, `packages/template`, apps):
`stack*`/`Stack*` → `hexclave*`/`Hexclave*` — incl.
`stackGlobalsSymbol`, the `_Stack*AppImpl` classes,
`stackAppInternalsSymbol`, `StackContext`, `getStackStripe`, etc. The
`stack*App` local-variable convention
(`stackServerApp`/`stackClientApp`/…) was renamed across 175
source/example/doc files.
- **File renames**: `hexclave-handler/provider/context.tsx`,
`backend/hexclave.tsx`, `internal-tool/hexclave.ts`,
`hexclave-app-internals.ts`.
- **Directory renames**: `lib/hexclave-app`, `hexclave-companion`,
`[...hexclave]` route segment, `skills/hexclave`,
`dashboard/src/hexclave`, and the package dirs
**`packages/{next,shared,ui,sc,cli}`** (dropping the `stack-` prefix to
match the `@hexclave/*` npm names).
- **Packages**: `@stackframe/template` → `@hexclave/template`; **deleted
`packages/init-stack`** (onboarding lives in `@hexclave/cli init`; the
published npm package is untouched).
- **Brand strings**: reworded `Stack Auth`/`Stack dashboard` prose in
code + docs-mintlify, renamed `hexclave-app.mdx`/`use-hexclave-app.mdx`
with redirects, regenerated OpenAPI, updated coupled e2e assertions;
`doctor`/`init` now prefer `hexclave.config.ts`.
### Intentionally kept (verified, not oversights)
Wire/compat identifiers (`x-stack-*` headers, `stack-*` cookies,
`STACK_*` env names, `*.stack-auth.com`, `stackauth_`, `ask_stack_auth`,
query params), public `Stack*` SDK aliases, crypto/JWT/vault
domain-separation tags, `*-brand-sentinel`s, the
`Symbol.for("StackAuth--…")` string, `_stack_sync_metadata`, Postgres
`stackframe` / docker image names, the `stack-auth-logo*.svg` (used by
the rebrand modal), and `migration.mdx` / "formerly known as Stack Auth"
notes. False positives (Phosphor `StackIcon`/`StackSimple`, `TanStack`,
`OrbStack`, `stackable`/`Stacked` charts) left alone.
### Review pass
Six review agents audited all commits. Found + fixed one real bug — a
build script (`bundle-type-definitions.ts`) hardcoded the old
`lib/stack-app` glob path (not an import, so typecheck/lint were blind),
silently emptying the dashboard AI type bundle — plus stale comments, a
dead CI env var, and stale `.gitignore`/`.dockerignore` entries.
Cross-cutting audit confirmed **zero wire-compat identifiers were
accidentally renamed**.
### ⚠️ Verification note
`typecheck` + `lint` are fully green locally. The **e2e suite was not
run** (needs a live backend+DB), so the brand-string assertion +
OpenAPI-regen changes are verified by grep/codegen only — please let CI
exercise e2e to confirm.
### Base-branch note
This branch was forked from the local-only `cl/friendly-lewin-72293f`
(not on origin, no separate PR), so this PR against `dev` also carries
that branch's ~11 preceding Hexclave-rename commits (config-file rename,
env-var dual-read, AI setup-prompt rebrand). If those should land
separately, re-parent before merge.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Finishes the internal Stack Auth → Hexclave rename and cleans up
remaining stragglers, including dev-tool and prompt copy. All changes
are internal-only; public/wire APIs remain unchanged. Re-merged `dev`
and resolved the payments create-purchase-url conflict.
- **Refactors**
- Internal symbols: stack*/Stack* → hexclave*/Hexclave* (e.g.,
`getHexclaveServerApp` via `@/hexclave`, `getHexclaveStripe`,
`hexclaveAppInternalsSymbol`, `hexclaveSchemaInfo`, Prisma
`__hexclave_*`, `data-hexclave-handler-page`, Stripe mock
`hexclavePortPrefix`).
- Files/dirs: moved to `lib/hexclave-app`; handler route
`[...hexclave]`; backend entry `src/hexclave.tsx`; dashboard internals
`hexclave-app-internals`; companion `hexclave-companion`; dropped
`stack-` prefix across package dirs
(`packages/{shared,ui,sc,cli,next}`); workflows/emulator paths now
`packages/cli`; Quetzal codegen env at `packages/next/.env.local`.
- Packages/docs: `@stackframe/template` → `@hexclave/template`; removed
`packages/init-stack`; regenerated OpenAPI and updated docs
slugs/redirects for hexclave-app/use-hexclave-app.
- Brand strings/prompts: reworded remaining “Stack” dashboard strings to
Hexclave; updated dev-tool copy and prompts; `doctor/init` now prefer
`hexclave.config.ts`. Kept all wire-compat identifiers and public
aliases (`x-stack-*`, `stack-*` cookies, `STACK_*` env,
`*.stack-auth.com`, `Stack*` SDK names).
- Rebased/merged onto latest `dev`: retained `@hexclave/template`, kept
`src` in published files, refreshed setup-prompt imports and docs JSON,
adopted 1.0.5 version bumps, and re-merged `dev` again (resolved
`create-purchase-url` with `getHexclaveStripe`).
- **Bug Fixes**
- Restored dashboard AI type bundle by pointing the glob to
`packages/template/src/lib/hexclave-app`.
- Addressed rename leftovers: updated lingering `@/stack` imports and
CSS selector, fixed schema/meta and port-prefix expansions, and aligned
emulator commands to `packages/cli`.
- CI/build: removed a dead env var and stale ignore entries; fixed
Docker by renaming `STACK_SKIP_TEMPLATE_GENERATION` →
`HEXCLAVE_SKIP_TEMPLATE_GENERATION`.
<sup>Written for commit 3c1af3bff3.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1547?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
593 lines
23 KiB
TypeScript
593 lines
23 KiB
TypeScript
import path from "path";
|
|
import { readFileSync } from "fs";
|
|
import { aiSetupPrompt, cliSetupPrompt, convexSetupPrompt, getSdkSetupPrompt, pythonBackendSetupPrompt, restApiBackendSetupPrompt, supabaseSetupPrompt } from "../packages/shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt";
|
|
import { deindent } from "../packages/shared/src/utils/strings";
|
|
import { writeFileSyncIfChanged } from "./utils";
|
|
import { remindersPrompt } from "../packages/shared/src/ai/unified-prompts/reminders";
|
|
import { buildLlmsFullTxt } from "../packages/shared/src/ai/llms/llms";
|
|
|
|
const generatedComment = "This file is auto-generated by scripts/generate-setup-prompt-docs.ts. Do not edit it manually; edit packages/shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts instead.";
|
|
type SdkSetupToolCategory = "frontend" | "backend" | "database" | "other";
|
|
type SdkSetupTool = {
|
|
label: string,
|
|
where: SdkSetupToolCategory[],
|
|
filterGroups: ("js" | "python" | "other")[],
|
|
imageUrl: string,
|
|
monochromeLogo: boolean,
|
|
tabs: { label: string, mdContent: string }[],
|
|
extraFeatures: string[],
|
|
};
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const docsJson = JSON.parse(readFileSync(path.join(repoRoot, "docs-mintlify/docs.json"), "utf-8"));
|
|
const setupPromptText = aiSetupPrompt;
|
|
|
|
const sdkSetupTools: Record<string, SdkSetupTool> = {
|
|
nextjs: {
|
|
label: "Next.js",
|
|
where: ["frontend", "backend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/nextjs.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "Next.js",
|
|
mdContent: getSdkSetupPrompt("nextjs"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
react: {
|
|
label: "React",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/react.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "React",
|
|
mdContent: getSdkSetupPrompt("react"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
js: {
|
|
label: "Other JS/TS",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/javascript.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "JS/TS",
|
|
mdContent: getSdkSetupPrompt("js"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
"tanstack-start": {
|
|
label: "Tanstack Start",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/tanstack.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "Tanstack Start",
|
|
mdContent: getSdkSetupPrompt("tanstack-start"),
|
|
}],
|
|
extraFeatures: ["tanstack-query"],
|
|
},
|
|
"tanstack-query": {
|
|
label: "Tanstack Query",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/tanstack.svg",
|
|
monochromeLogo: true,
|
|
tabs: [],
|
|
extraFeatures: ["tanstack-query"],
|
|
},
|
|
nodejs: {
|
|
label: "Node.js",
|
|
where: ["backend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/nodejs.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Node.js",
|
|
mdContent: getSdkSetupPrompt("nodejs"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
bun: {
|
|
label: "Bun",
|
|
where: ["backend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/bun.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Bun",
|
|
mdContent: getSdkSetupPrompt("bun"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
python: {
|
|
label: "Python",
|
|
where: ["backend"],
|
|
filterGroups: ["python"],
|
|
imageUrl: "/images/setup-tools/python.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Python",
|
|
mdContent: pythonBackendSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
"rest-api": {
|
|
label: "Other (REST API)",
|
|
where: ["backend"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/cli.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "Other (REST API)",
|
|
mdContent: restApiBackendSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
convex: {
|
|
label: "Convex",
|
|
where: ["backend", "database"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/convex.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Convex",
|
|
mdContent: convexSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
supabase: {
|
|
label: "Supabase",
|
|
where: ["database"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/supabase.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Supabase",
|
|
mdContent: supabaseSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
cli: {
|
|
label: "CLI",
|
|
where: ["other"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/cli.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "CLI",
|
|
mdContent: cliSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
/*mcp: {
|
|
label: "MCP",
|
|
where: ["other"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/mcp.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "MCP",
|
|
mdContent: mcpSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},*/
|
|
};
|
|
|
|
function slugify(value: string) {
|
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
}
|
|
|
|
const categoryLabels = new Map<SdkSetupToolCategory, string>([
|
|
["frontend", "Frontend"],
|
|
["backend", "Backend"],
|
|
["database", "Database"],
|
|
["other", "Other"],
|
|
]);
|
|
|
|
const setupToolIds = Object.keys(sdkSetupTools);
|
|
const setupTabs = Object.entries(sdkSetupTools).flatMap(([toolId, tool]) => {
|
|
return tool.tabs.map((tab, tabIndex) => ({
|
|
id: `${toolId}-${slugify(tab.label)}-${tabIndex}`,
|
|
toolLabel: tool.label,
|
|
toolId,
|
|
tabLabel: tab.label,
|
|
mdContent: tab.mdContent,
|
|
}));
|
|
});
|
|
const setupTabMetadata = setupTabs.map((tab) => ({
|
|
toolId: tab.toolId,
|
|
title: tab.tabLabel,
|
|
}));
|
|
const unifiedAiPromptTabTitle = "Unified AI Prompt";
|
|
|
|
function renderToolCards(category: SdkSetupToolCategory) {
|
|
const tools = Object.entries(sdkSetupTools).filter(([, tool]) => tool.where.includes(category));
|
|
return tools.map(([toolId, tool]) => {
|
|
const hasTabs = tool.tabs.length > 0;
|
|
const iconMarkup = tool.monochromeLogo
|
|
? deindent`
|
|
<span
|
|
aria-hidden="true"
|
|
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
|
|
style={{
|
|
WebkitMask: "url(${tool.imageUrl}) center / contain no-repeat",
|
|
mask: "url(${tool.imageUrl}) center / contain no-repeat",
|
|
}}
|
|
/>
|
|
`
|
|
: deindent`
|
|
<img
|
|
src="${tool.imageUrl}"
|
|
alt=""
|
|
aria-hidden="true"
|
|
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
|
|
/>
|
|
`;
|
|
return deindent`
|
|
<button
|
|
type="button"
|
|
aria-pressed="false"
|
|
data-setup-tool-card="true"
|
|
data-tool-id="${toolId}"
|
|
data-tool-label="${tool.label}"
|
|
data-tool-has-tabs="${hasTabs ? "true" : "false"}"
|
|
data-tool-extra-features="${tool.extraFeatures.join(",")}"
|
|
data-tool-filter-groups="${tool.filterGroups.join(",")}"
|
|
onClick={onSetupToolClick}
|
|
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
|
|
title="${tool.label}"
|
|
>
|
|
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
|
|
${iconMarkup}
|
|
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
|
|
</div>
|
|
<span
|
|
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
|
|
title="${tool.label}"
|
|
>
|
|
${tool.label}
|
|
</span>
|
|
</button>
|
|
`;
|
|
}).join("\n");
|
|
}
|
|
|
|
function renderToolCategory(category: SdkSetupToolCategory) {
|
|
return deindent`
|
|
<section data-setup-tool-category="true" className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
|
|
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">${categoryLabels.get(category)}</h3>
|
|
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
|
|
${renderToolCards(category)}
|
|
</div>
|
|
</section>
|
|
`;
|
|
}
|
|
|
|
function renderSetupFilterButton(filterId: string, label: string) {
|
|
return deindent`
|
|
<button
|
|
type="button"
|
|
aria-pressed="true"
|
|
data-setup-filter-button="true"
|
|
data-filter-id="${filterId}"
|
|
onClick={onSetupFilterClick}
|
|
className="inline-flex items-center justify-center rounded-full px-2.5 py-1 text-xs font-medium text-[#526994] transition-colors duration-150 hover:transition-none hover:bg-white/45 hover:text-[#2a4272] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/70 aria-pressed:text-[#243d70] aria-pressed:shadow-sm dark:text-[#9eb3d8] dark:hover:bg-white/10 dark:hover:text-white dark:aria-pressed:bg-white/14 dark:aria-pressed:text-white"
|
|
>
|
|
${label}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
function renderMarkdownInTabPanel(mdContent: string) {
|
|
return mdContent.replace(/<Note>\n([\s\S]*?)\n\s*<\/Note>/g, (_match, noteContent: string) => {
|
|
const blockquoteContent = deindent(noteContent)
|
|
.split("\n")
|
|
.map((line) => line.length === 0 ? ">" : `> ${line}`)
|
|
.join("\n");
|
|
return blockquoteContent;
|
|
});
|
|
}
|
|
|
|
function renderTabPanels() {
|
|
return setupTabs.map((tab) => deindent`
|
|
<Tab title="${tab.tabLabel}">
|
|
<div className="not-prose mb-1 flex justify-end">
|
|
<button
|
|
type="button"
|
|
onClick={copyGeneratedSetupPrompt}
|
|
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
|
|
>
|
|
Copy prompt
|
|
</button>
|
|
</div>
|
|
|
|
${renderMarkdownInTabPanel(tab.mdContent)}
|
|
</Tab>
|
|
`).join("\n\n");
|
|
}
|
|
|
|
function renderUnifiedAiPromptTab() {
|
|
return deindent`
|
|
<Tab title="${unifiedAiPromptTabTitle}">
|
|
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>
|
|
<button
|
|
type="button"
|
|
onClick={copyGeneratedSetupPrompt}
|
|
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
|
|
>
|
|
Copy prompt
|
|
</button>
|
|
</div>
|
|
</Tab>
|
|
`;
|
|
}
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "packages/shared/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts"),
|
|
deindent`
|
|
// This file is generated from docs-mintlify/docs.json.
|
|
|
|
const docsJson = ${JSON.stringify(docsJson, null, 2)} as const;
|
|
|
|
export default docsJson;
|
|
` + "\n",
|
|
);
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/snippets/home-prompt-island.jsx"),
|
|
deindent`
|
|
// ${generatedComment}
|
|
|
|
export const generatedSetupPromptText = ${JSON.stringify(setupPromptText)};
|
|
export const setupToolIds = ${JSON.stringify(setupToolIds)};
|
|
export const setupTabMetadata = ${JSON.stringify(setupTabMetadata)};
|
|
export const unifiedAiPromptTabTitle = ${JSON.stringify(unifiedAiPromptTabTitle)};
|
|
|
|
export const GeneratedSetupPromptText = ({ className }) => (
|
|
<textarea
|
|
readOnly
|
|
aria-label="Generated setup prompt"
|
|
value={generatedSetupPromptText}
|
|
className={className}
|
|
/>
|
|
);
|
|
` + "\n",
|
|
);
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/guides/getting-started/setup.mdx"),
|
|
deindent`
|
|
---
|
|
title: Setup
|
|
description: Install and configure Hexclave for your project
|
|
sidebarTitle: Setup
|
|
---
|
|
|
|
{/* ${generatedComment} */}
|
|
|
|
export const generatedSetupPromptText = ${JSON.stringify(setupPromptText)};
|
|
export const setupToolIds = ${JSON.stringify(setupToolIds)};
|
|
export const setupTabMetadata = ${JSON.stringify(setupTabMetadata)};
|
|
import { hexclaveAgentRemindersText } from "/snippets/hexclave-agent-reminders.jsx";
|
|
export const unifiedAiPromptTabTitle = ${JSON.stringify(unifiedAiPromptTabTitle)};
|
|
|
|
export const copyGeneratedSetupPrompt = async (event) => {
|
|
const button = event.currentTarget;
|
|
try {
|
|
await navigator.clipboard.writeText(generatedSetupPromptText);
|
|
button.textContent = "Copied";
|
|
} catch {
|
|
button.textContent = "Copy failed";
|
|
}
|
|
window.setTimeout(() => {
|
|
button.textContent = "Copy prompt";
|
|
}, 1300);
|
|
};
|
|
|
|
export const getSelectedSetupToolIdsFromUrl = () => {
|
|
if (typeof window === "undefined") {
|
|
return [];
|
|
}
|
|
const selectedToolIds = new Set(setupToolIds);
|
|
return (new URLSearchParams(window.location.search).get("tools") ?? "")
|
|
.split(",")
|
|
.map((toolId) => toolId.trim())
|
|
.filter((toolId) => selectedToolIds.has(toolId));
|
|
};
|
|
|
|
export const writeSelectedSetupToolIdsToUrl = (selectedToolIds) => {
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
const url = new URL(window.location.href);
|
|
const orderedSelectedToolIds = setupToolIds.filter((toolId) => selectedToolIds.has(toolId));
|
|
if (orderedSelectedToolIds.length === 0) {
|
|
url.searchParams.delete("tools");
|
|
} else {
|
|
url.searchParams.set("tools", orderedSelectedToolIds.join(","));
|
|
}
|
|
window.history.replaceState(null, "", url.pathname + url.search + url.hash);
|
|
};
|
|
|
|
export const updateSetupBuilder = (root, syncUrl = true) => {
|
|
const activeFilterIds = new Set(
|
|
Array.from(root.querySelectorAll("[data-setup-filter-button='true'][aria-pressed='true']"))
|
|
.map((button) => button.getAttribute("data-filter-id"))
|
|
.filter((filterId) => filterId != null)
|
|
);
|
|
for (const toolCard of root.querySelectorAll("[data-setup-tool-card='true']")) {
|
|
const filterGroups = (toolCard.getAttribute("data-tool-filter-groups") ?? "")
|
|
.split(",")
|
|
.filter((filterGroup) => filterGroup.length > 0);
|
|
const shouldShow = filterGroups.some((filterGroup) => activeFilterIds.has(filterGroup));
|
|
toolCard.hidden = !shouldShow;
|
|
toolCard.style.display = shouldShow ? "" : "none";
|
|
}
|
|
for (const toolCategory of root.querySelectorAll("[data-setup-tool-category='true']")) {
|
|
const hasVisibleCard = Array.from(toolCategory.querySelectorAll("[data-setup-tool-card='true']"))
|
|
.some((toolCard) => !toolCard.hidden);
|
|
toolCategory.hidden = !hasVisibleCard;
|
|
toolCategory.style.display = hasVisibleCard ? "" : "none";
|
|
}
|
|
const selectedToolIds = new Set(
|
|
Array.from(root.querySelectorAll("[data-setup-tool-card='true'][aria-pressed='true']"))
|
|
.filter((card) => !card.hidden)
|
|
.map((card) => card.getAttribute("data-tool-id"))
|
|
.filter((toolId) => toolId != null)
|
|
);
|
|
if (syncUrl) {
|
|
writeSelectedSetupToolIdsToUrl(selectedToolIds);
|
|
}
|
|
const visibleTabTitles = new Set(setupTabMetadata
|
|
.filter((tab) => selectedToolIds.has(tab.toolId))
|
|
.map((tab) => tab.title)
|
|
);
|
|
if (visibleTabTitles.size > 0) {
|
|
visibleTabTitles.add(unifiedAiPromptTabTitle);
|
|
}
|
|
const tabsRoot = root.querySelector("[data-setup-tabs-root='true']");
|
|
|
|
const emptyState = root.querySelector("[data-setup-tabs-empty='true']");
|
|
if (emptyState != null) {
|
|
emptyState.hidden = visibleTabTitles.size > 0;
|
|
emptyState.style.display = visibleTabTitles.size > 0 ? "none" : "";
|
|
}
|
|
if (tabsRoot == null) {
|
|
return;
|
|
}
|
|
|
|
tabsRoot.hidden = visibleTabTitles.size === 0;
|
|
tabsRoot.style.display = visibleTabTitles.size === 0 ? "none" : "";
|
|
const tabButtons = Array.from(tabsRoot.querySelectorAll("[role='tab']"));
|
|
let firstVisibleTabButton = null;
|
|
let selectedVisibleTabButton = null;
|
|
for (const tabButton of tabButtons) {
|
|
const title = tabButton.textContent?.trim() ?? "";
|
|
const shouldShow = visibleTabTitles.has(title);
|
|
tabButton.hidden = !shouldShow;
|
|
tabButton.style.display = shouldShow ? "" : "none";
|
|
if (shouldShow && firstVisibleTabButton == null) {
|
|
firstVisibleTabButton = tabButton;
|
|
}
|
|
if (shouldShow && tabButton.getAttribute("aria-selected") === "true") {
|
|
selectedVisibleTabButton = tabButton;
|
|
}
|
|
}
|
|
if (visibleTabTitles.size > 0 && selectedVisibleTabButton == null) {
|
|
firstVisibleTabButton?.click();
|
|
}
|
|
};
|
|
|
|
export const initializeSetupBuilder = (node) => {
|
|
if (node == null || node.dataset.setupBuilderInitialized === "true") {
|
|
return;
|
|
}
|
|
node.dataset.setupBuilderInitialized = "true";
|
|
const selectedToolIds = new Set(getSelectedSetupToolIdsFromUrl());
|
|
for (const toolCard of node.querySelectorAll("[data-setup-tool-card='true']")) {
|
|
toolCard.setAttribute("aria-pressed", selectedToolIds.has(toolCard.getAttribute("data-tool-id")) ? "true" : "false");
|
|
}
|
|
updateSetupBuilder(node, false);
|
|
};
|
|
|
|
export const onSetupToolClick = (event) => {
|
|
const button = event.currentTarget;
|
|
const root = button.closest("[data-setup-builder='true']");
|
|
if (root == null) {
|
|
return;
|
|
}
|
|
button.setAttribute("aria-pressed", button.getAttribute("aria-pressed") === "true" ? "false" : "true");
|
|
updateSetupBuilder(root);
|
|
};
|
|
|
|
export const onSetupFilterClick = (event) => {
|
|
const button = event.currentTarget;
|
|
const root = button.closest("[data-setup-builder='true']");
|
|
if (root == null) {
|
|
return;
|
|
}
|
|
const nextPressed = button.getAttribute("aria-pressed") !== "true";
|
|
const currentlyPressedFilters = root.querySelectorAll("[data-setup-filter-button='true'][aria-pressed='true']").length;
|
|
if (!nextPressed && currentlyPressedFilters <= 1) {
|
|
return;
|
|
}
|
|
button.setAttribute("aria-pressed", nextPressed ? "true" : "false");
|
|
updateSetupBuilder(root);
|
|
};
|
|
|
|
<Visibility for="agents">
|
|
{hexclaveAgentRemindersText}
|
|
</Visibility>
|
|
|
|
<Note>
|
|
<p className="font-semibold">Setting up with AI? Use this single prompt:</p>
|
|
<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>
|
|
<button
|
|
type="button"
|
|
onClick={copyGeneratedSetupPrompt}
|
|
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 focus-visible:ring-offset-[#f3f6ff] dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51] dark:focus-visible:ring-offset-[#0f1a2e]"
|
|
>
|
|
Copy prompt
|
|
</button>
|
|
</div>
|
|
</Note>
|
|
|
|
<div ref={initializeSetupBuilder} data-setup-builder="true" className="mt-10">
|
|
<div>
|
|
<h2 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">Choose your tech stack</h2>
|
|
<p className="mt-2 text-sm font-medium text-slate-500 dark:text-slate-400">Choose all that apply.</p>
|
|
</div>
|
|
|
|
<div className="not-prose mt-5 space-y-4 rounded-2xl border border-[#d6e4ff] bg-gradient-to-b from-[#f7faff] to-[#eaf2ff] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_10px_30px_-24px_rgba(47,79,140,0.35)] dark:border-[#1f2d45] dark:from-[#11203a] dark:to-[#070f1f] dark:shadow-[inset_0_1px_0_rgba(112,152,224,0.18),0_16px_34px_-24px_rgba(2,8,20,0.85)] sm:p-4">
|
|
<div className="flex flex-col gap-2 border-b border-[#d6e4ff]/80 pb-3 dark:border-[#263a5f] sm:flex-row sm:items-center">
|
|
<span className="text-sm font-medium text-[#526994] dark:text-[#9eb3d8]">Filter:</span>
|
|
<div className="flex flex-wrap gap-2">
|
|
${renderSetupFilterButton("js", "JS")}
|
|
${renderSetupFilterButton("python", "Python")}
|
|
${renderSetupFilterButton("other", "Other")}
|
|
</div>
|
|
</div>
|
|
${renderToolCategory("frontend")}
|
|
${renderToolCategory("backend")}
|
|
${renderToolCategory("database")}
|
|
${renderToolCategory("other")}
|
|
</div>
|
|
|
|
<div className="mt-8">
|
|
<p data-setup-tabs-empty="true" className="not-prose rounded-2xl border border-[#c5d7f6] bg-white/65 p-5 text-sm text-[#4a5f89] dark:border-[#2c4c7d] dark:bg-[#0c1627]/45 dark:text-[#8fa4cc]">
|
|
Select a tool to show setup instructions.
|
|
</p>
|
|
<div data-setup-tabs-root="true" hidden style={{ display: "none" }}>
|
|
<Tabs>
|
|
${renderUnifiedAiPromptTab()}
|
|
|
|
${renderTabPanels()}
|
|
</Tabs>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
` + "\n",
|
|
);
|
|
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/snippets/hexclave-agent-reminders.jsx"),
|
|
deindent`
|
|
export const hexclaveAgentRemindersText = ${JSON.stringify(remindersPrompt)};
|
|
` + "\n",
|
|
);
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/llms-full.txt"),
|
|
buildLlmsFullTxt(docsJson).replace(/[ \t]+$/gm, "") + "\n",
|
|
);
|