From 92c332ad3f86de036a2b0661992af0d5bd1fa3f6 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Wed, 10 Sep 2025 11:42:45 -0700 Subject: [PATCH 1/4] init stack args --- .../new-project/page-client.tsx | 1 - packages/init-stack/package.json | 4 ++- packages/init-stack/src/index.ts | 33 +++++++++++-------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx index 175c4d0ba1..be3a19ee3c 100644 --- a/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx @@ -146,7 +146,6 @@ export default function PageClient() { -
diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json index f228e4bf0e..6115834edd 100644 --- a/packages/init-stack/package.json +++ b/packages/init-stack/package.json @@ -24,7 +24,9 @@ "test-run-js:manual": "rimraf test-run-output && npx -y sv create test-run-output --no-install && pnpm run init-stack:local test-run-output", "test-run-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output --js --client --npm", "test-run-next:manual": "rimraf test-run-output && npx -y create-next-app@latest test-run-output && pnpm run init-stack:local test-run-output", - "test-run-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output" + "test-run-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output", + "test-run-keys-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output --project-id my-project-id --publishable-client-key my-publishable-client-key", + "test-run-keys-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output --js --client --npm --project-id my-project-id --publishable-client-key my-publishable-client-key" }, "files": [ "README.md", diff --git a/packages/init-stack/src/index.ts b/packages/init-stack/src/index.ts index 11059a1780..5d103dee02 100644 --- a/packages/init-stack/src/index.ts +++ b/packages/init-stack/src/index.ts @@ -42,6 +42,8 @@ program .option("--bun", "Use bun as package manager") .option("--client", "Initialize client-side only") .option("--server", "Initialize server-side only") + .option("--project-id ", "Project ID to use in setup") + .option("--publishable-client-key ", "Publishable client key to use in setup") .option("--no-browser", "Don't open browser for environment variable setup") .addHelpText('after', ` For more information, please visit https://docs.stack-auth.com/getting-started/setup`); @@ -58,6 +60,8 @@ const typeFromArgs: string | undefined = options.js ? "js" : options.next ? "nex const packageManagerFromArgs: string | undefined = options.npm ? "npm" : options.yarn ? "yarn" : options.pnpm ? "pnpm" : options.bun ? "bun" : undefined; const isClient: boolean = options.client || false; const isServer: boolean = options.server || false; +const projectIdFromArgs: string | undefined = options.projectId; +const publishableClientKeyFromArgs: string | undefined = options.publishableClientKey; // Commander negates the boolean options with prefix `--no-` // so `--no-browser` becomes `browser: false` const noBrowser: boolean = !options.browser; @@ -195,6 +199,7 @@ async function main(): Promise { if (type === "next") { const projectInfo = await Steps.getNextProjectInfo({ packageJson: projectPackageJson }); await Steps.updateNextLayoutFile(projectInfo); + await Steps.writeStackAppFile(projectInfo, "client"); await Steps.writeStackAppFile(projectInfo, "server"); await Steps.writeNextHandlerFile(projectInfo); await Steps.writeNextLoadingFile(projectInfo); @@ -511,13 +516,13 @@ const Steps = { "# 1. Go to https://app.stack-auth.com\n" + "# 2. Create a new project\n" + "# 3. Copy the keys below\n" + - "NEXT_PUBLIC_STACK_PROJECT_ID=\n" + - "NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=\n" + + `NEXT_PUBLIC_STACK_PROJECT_ID=${projectIdFromArgs ?? ""}\n` + + `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${publishableClientKeyFromArgs ?? ""}\n` + "STACK_SECRET_SERVER_KEY=\n" : "# Stack Auth keys\n" + "# Get these variables by creating a project on https://app.stack-auth.com.\n" + - "NEXT_PUBLIC_STACK_PROJECT_ID=\n" + - "NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=\n" + + `NEXT_PUBLIC_STACK_PROJECT_ID=${projectIdFromArgs ?? ""}\n` + + `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${publishableClientKeyFromArgs ?? ""}\n` + "STACK_SECRET_SERVER_KEY=\n"; laterWriteFile(envLocalPath, envContent); @@ -567,18 +572,14 @@ const Steps = { return res; }, - async writeStackAppFile({ type, srcPath, defaultExtension, indentation }: StackAppFileOptions, clientOrServer: string): Promise { + async writeStackAppFile({ type, srcPath, defaultExtension, indentation }: StackAppFileOptions, clientOrServer: "server" | "client"): Promise { const packageName = await Steps.getStackPackageName(type); const clientOrServerCap = { client: "Client", server: "Server", - }[clientOrServer] ?? throwErr("unknown clientOrServer " + clientOrServer); - - const relativeStackAppPath = { - js: `stack/${clientOrServer}`, - next: "stack", - }[type] ?? throwErr("unknown type"); + }[clientOrServer as string] ?? throwErr("unknown clientOrServer " + clientOrServer); + const relativeStackAppPath = `stack/${clientOrServer}`; const stackAppPathWithoutExtension = path.join(srcPath, relativeStackAppPath); const stackAppFileExtension = @@ -596,6 +597,11 @@ const Steps = { `It seems that you already installed Stack in this project.` ); } + + const publishableClientKeyWrite = clientOrServer === "server" + ? `process.env.STACK_PUBLISHABLE_CLIENT_KEY ${publishableClientKeyFromArgs ? `|| '${publishableClientKeyFromArgs}'` : ""}` + : `'${publishableClientKeyFromArgs ?? 'INSERT_YOUR_PUBLISHABLE_CLIENT_KEY_HERE'}'`; + laterWriteFileIfNotExists( stackAppPath, ` @@ -606,7 +612,8 @@ import { Stack${clientOrServerCap}App } from ${JSON.stringify(packageName)}; export const stack${clientOrServerCap}App = new Stack${clientOrServerCap}App({ ${indentation}tokenStore: ${type === "next" ? '"nextjs-cookie"' : (clientOrServer === "client" ? '"cookie"' : '"memory"')},${ type === "js" ? `\n\n${indentation}// get your Stack Auth API keys from https://app.stack-auth.com${clientOrServer === "client" ? ` and store them in a safe place (eg. environment variables)` : ""}` : ""}${ -type === "js" ? `\n${indentation}publishableClientKey: ${clientOrServer === "server" ? 'process.env.STACK_PUBLISHABLE_CLIENT_KEY' : 'INSERT_YOUR_PUBLISHABLE_CLIENT_KEY_HERE'},` : ""}${ +type === "js" && projectIdFromArgs ? `\n${indentation}projectId: '${projectIdFromArgs}',` : ""}${ +type === "js" ? `\n${indentation}publishableClientKey: ${publishableClientKeyWrite},` : ""}${ type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey: process.env.STACK_SECRET_SERVER_KEY,` : ""} }); `.trim() + "\n" @@ -684,7 +691,7 @@ type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey: } }, - async getServerOrClientOrBoth(): Promise { + async getServerOrClientOrBoth(): Promise> { if (isClient && isServer) return ["server", "client"]; if (isServer) return ["server"]; if (isClient) return ["client"]; From cf8f1bd1085b3081c35f44746ecde86b939b6292 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Wed, 10 Sep 2025 17:43:14 -0700 Subject: [PATCH 2/4] fix imports --- packages/init-stack/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/init-stack/src/index.ts b/packages/init-stack/src/index.ts index 5d103dee02..6cd8cc8520 100644 --- a/packages/init-stack/src/index.ts +++ b/packages/init-stack/src/index.ts @@ -637,7 +637,7 @@ type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey: } laterWriteFileIfNotExists( handlerPath, - `import { StackHandler } from "@stackframe/stack";\nimport { stackServerApp } from "../../../stack";\n\nexport default function Handler(props${ + `import { StackHandler } from "@stackframe/stack";\nimport { stackServerApp } from "../../../stack/server";\n\nexport default function Handler(props${ handlerFileExtension.includes("ts") ? ": unknown" : "" }) {\n${projectInfo.indentation}return ;\n}\n` ); @@ -749,7 +749,7 @@ async function getUpdatedLayout(originalLayout: string): Promise Date: Wed, 10 Sep 2025 18:34:23 -0700 Subject: [PATCH 3/4] fix docs --- .../[projectId]/(overview)/setup-page.tsx | 8 ++++---- docs/templates/concepts/oauth.mdx | 2 +- docs/templates/customization/custom-pages.mdx | 8 ++++---- .../page-examples/forgot-password.mdx | 2 +- .../page-examples/password-reset.mdx | 2 +- .../templates/getting-started/example-pages.mdx | 12 ++++++------ docs/templates/getting-started/setup.mdx | 17 +++++++++-------- docs/templates/getting-started/users.mdx | 2 +- packages/init-stack/src/index.ts | 4 ++-- 9 files changed, 29 insertions(+), 28 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx index 19f2e03ab1..133e0822db 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx @@ -114,10 +114,10 @@ export default function SetupPage(props: { toMetrics: () => void }) { }, { step: 4, - title: "Create stack.ts file", + title: "Create stack/client.ts file", content: <> - Create a new file called stack.ts and add the following code. Here we use react-router-dom as an example. + Create a new file called stack/client.ts and add the following code. Here we use react-router-dom as an example. void }) { } }); `} - title="stack.ts" + title="stack/client.ts" icon="code" /> @@ -154,7 +154,7 @@ export default function SetupPage(props: { toMetrics: () => void }) { import { StackHandler, StackProvider, StackTheme } from "@stackframe/react"; import { Suspense } from "react"; import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom"; - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; function HandlerRoutes() { const location = useLocation(); diff --git a/docs/templates/concepts/oauth.mdx b/docs/templates/concepts/oauth.mdx index 41d068fb43..a6c3536641 100644 --- a/docs/templates/concepts/oauth.mdx +++ b/docs/templates/concepts/oauth.mdx @@ -91,7 +91,7 @@ To avoid showing the authorization page twice, you can already request scopes du To do this, edit the `oauthScopesOnSignIn` setting of your `stackServerApp`: -```jsx title='stack.ts' +```jsx title='stack/server.ts' export const stackServerApp = new StackServerApp({ // ...your other settings... oauthScopesOnSignIn: { diff --git a/docs/templates/customization/custom-pages.mdx b/docs/templates/customization/custom-pages.mdx index 839d0e6fd7..c452aaadfe 100644 --- a/docs/templates/customization/custom-pages.mdx +++ b/docs/templates/customization/custom-pages.mdx @@ -23,9 +23,9 @@ export default function CustomSignInPage() { } ``` -Then you can instruct the Stack app in `stack.ts` to use your custom sign in page: +Then you can instruct the Stack app in `stack/server.ts` to use your custom sign in page: -```tsx title="stack.ts" +```tsx title="stack/server.ts" export const stackServerApp = new StackServerApp({ // ... // add these three lines @@ -67,9 +67,9 @@ export default function CustomOAuthSignIn() { } ``` -Again, edit the Stack app in `stack.ts` to use your custom sign in page: +Again, edit the Stack app in `stack/server.ts` to use your custom sign in page: -```tsx title="stack.ts" +```tsx title="stack/server.ts" export const stackServerApp = new StackServerApp({ // ... // add these three lines diff --git a/docs/templates/customization/page-examples/forgot-password.mdx b/docs/templates/customization/page-examples/forgot-password.mdx index a6c942129a..7da437e827 100644 --- a/docs/templates/customization/page-examples/forgot-password.mdx +++ b/docs/templates/customization/page-examples/forgot-password.mdx @@ -22,7 +22,7 @@ export default function DefaultForgotPassword() { To integrate the forgot password page with your application's routing: 1. Create a route for your forgot password page (e.g., `/forgot-password`) -2. Configure Stack Auth to use your custom route in your `stack.ts` file: +2. Configure Stack Auth to use your custom route in your `stack/server.ts` file: ```tsx export const stackServerApp = new StackServerApp({ diff --git a/docs/templates/customization/page-examples/password-reset.mdx b/docs/templates/customization/page-examples/password-reset.mdx index 51523cc245..e10681337d 100644 --- a/docs/templates/customization/page-examples/password-reset.mdx +++ b/docs/templates/customization/page-examples/password-reset.mdx @@ -23,7 +23,7 @@ To integrate the password reset page with your application's routing: 1. Create a route handler that extracts the reset code from the URL (e.g., `/reset-password?code=xyz123`) 2. Pass the code to your password reset component -3. Configure Stack Auth to use your custom route in your `stack.ts` file: +3. Configure Stack Auth to use your custom route in your `stack/server.ts` file: ```tsx export const stackServerApp = new StackServerApp({ diff --git a/docs/templates/getting-started/example-pages.mdx b/docs/templates/getting-started/example-pages.mdx index e4d3240b5e..913db8b005 100644 --- a/docs/templates/getting-started/example-pages.mdx +++ b/docs/templates/getting-started/example-pages.mdx @@ -7,7 +7,7 @@ This guide demonstrates how to integrate Stack Auth with Vite. The same principl ### Initialize the app -```typescript title="stack.ts" +```typescript title="stack/client.ts" import { StackClientApp } from "@stackframe/js"; // Add type declaration for Vite's import.meta.env @@ -76,7 +76,7 @@ export const stackClientApp = new StackClientApp({ ```typescript - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; const updateUIState = (user: any | null) => { const authOptions = document.getElementById("authOptions"); @@ -148,7 +148,7 @@ export const stackClientApp = new StackClientApp({ ```typescript - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; // Check if user is already signed in stackClientApp.getUser().then((user) => { @@ -253,7 +253,7 @@ export const stackClientApp = new StackClientApp({ ```typescript - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; // Check if user is already signed in stackClientApp.getUser().then((user) => { @@ -335,7 +335,7 @@ export const stackClientApp = new StackClientApp({ ```typescript - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; // Check if user is already signed in stackClientApp.getUser().then((user) => { @@ -413,7 +413,7 @@ export const stackClientApp = new StackClientApp({ ```typescript - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; // Check if user is already signed in stackClientApp.getUser().then((user) => { diff --git a/docs/templates/getting-started/setup.mdx b/docs/templates/getting-started/setup.mdx index 917fdfb093..a0b3275c7e 100644 --- a/docs/templates/getting-started/setup.mdx +++ b/docs/templates/getting-started/setup.mdx @@ -50,7 +50,8 @@ We recommend using our **setup wizard** for a seamless installation experience. - `app/handler/[...stack]/page.tsx`: This file contains the default pages for sign-in, sign-out, account settings, and more. If you prefer, later you will learn how to [use custom pages](../customization/custom-pages.mdx) instead. - `app/layout.tsx`: The layout file was updated to wrap the entire body with `StackProvider` and `StackTheme`. - `app/loading.tsx`: If not yet found, Stack automatically adds a Suspense boundary to your app. This is shown to the user while Stack's async hooks, like `useUser`, are loading. - - `stack.ts`: This file contains the `stackServerApp` which you can use to access Stack from Server Components, Server Actions, API routes, and middleware. + - `stack/server.ts`: This file contains the `stackServerApp` which you can use to access Stack from Server Components, Server Actions, API routes, and middleware. + - `stack/client.ts`: This file contains the `stackClientApp` which you can use to access Stack from Client Components @@ -82,11 +83,11 @@ We recommend using our **setup wizard** for a seamless installation experience. ``` - ### Create `stack.ts` file + ### Create `stack/server.ts` file - Create a new file `stack.ts` in your root directory and fill it with the following: + Create a new file `stack/server.ts` in your root directory and fill it with the following: - ```tsx title="stack.ts" + ```tsx title="stack/server.ts" import "server-only"; import { StackServerApp } from "@stackframe/stack"; @@ -202,11 +203,11 @@ Before getting started, make sure you have a [React project](https://react.dev/l If you haven't already, [register a new account on Stack](https://app.stack-auth.com/projects), create a project in the dashboard, create a new API key from the left sidebar, and copy the project ID, publishable client key, and secret server key into a new file called `.env.local` in the root of your React project: - ### Create `stack.ts` file + ### Create `stack/client.ts` file - Create a new file `stack.ts` in your root directory and fill it with the following Stack app initialization code: + Create a new file `stack/client.ts` in your root directory and fill it with the following Stack app initialization code: - ```tsx title="stack.ts" + ```tsx title="stack/client.ts" import { StackClientApp } from "@stackframe/react"; import { useNavigate } from "react-router-dom"; @@ -231,7 +232,7 @@ Before getting started, make sure you have a [React project](https://react.dev/l import { StackHandler, StackProvider, StackTheme } from "@stackframe/react"; import { Suspense } from "react"; import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom"; - import { stackClientApp } from "./stack"; + import { stackClientApp } from "./stack/client"; function HandlerRoutes() { const location = useLocation(); diff --git a/docs/templates/getting-started/users.mdx b/docs/templates/getting-started/users.mdx index a60969f789..2e30184c23 100644 --- a/docs/templates/getting-started/users.mdx +++ b/docs/templates/getting-started/users.mdx @@ -30,7 +30,7 @@ Sometimes, you want to retrieve the user only if they're signed in, and redirect ## Server Component basics -Since `useUser()` is a stateful hook, you can't use it on server components. Instead, you can import `stackServerApp` from `stack.ts` and call `getUser()`: +Since `useUser()` is a stateful hook, you can't use it on server components. Instead, you can import `stackServerApp` from `stack/client.ts` and call `getUser()`: ```tsx title="my-server-component.tsx" import { stackServerApp } from "@/stack"; diff --git a/packages/init-stack/src/index.ts b/packages/init-stack/src/index.ts index 6cd8cc8520..d4e9f3200d 100644 --- a/packages/init-stack/src/index.ts +++ b/packages/init-stack/src/index.ts @@ -590,7 +590,7 @@ const Steps = { if (stackAppContent) { if (!stackAppContent.includes("@stackframe/")) { throw new UserError( - `A file at the path ${stackAppPath} already exists. Stack uses the stack.ts file to initialize the Stack SDK. Please remove the existing file and try again.` + `A file at the path ${stackAppPath} already exists. Stack uses the stack/${clientOrServer}.ts file to initialize the Stack SDK. Please remove the existing file and try again.` ); } throw new UserError( @@ -605,7 +605,7 @@ const Steps = { laterWriteFileIfNotExists( stackAppPath, ` -${type === "next" ? `import "server-only";` : ""} +${type === "next" && clientOrServer === "server" ? `import "server-only";` : ""} import { Stack${clientOrServerCap}App } from ${JSON.stringify(packageName)}; From b204910ebdde0989079ba5a278040f5faf11cd10 Mon Sep 17 00:00:00 2001 From: Bilal Godil Date: Wed, 10 Sep 2025 18:39:03 -0700 Subject: [PATCH 4/4] fix .env --- packages/init-stack/src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/init-stack/src/index.ts b/packages/init-stack/src/index.ts index d4e9f3200d..1c82fd3a0f 100644 --- a/packages/init-stack/src/index.ts +++ b/packages/init-stack/src/index.ts @@ -516,13 +516,13 @@ const Steps = { "# 1. Go to https://app.stack-auth.com\n" + "# 2. Create a new project\n" + "# 3. Copy the keys below\n" + - `NEXT_PUBLIC_STACK_PROJECT_ID=${projectIdFromArgs ?? ""}\n` + - `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${publishableClientKeyFromArgs ?? ""}\n` + + `NEXT_PUBLIC_STACK_PROJECT_ID="${projectIdFromArgs ?? ""}"\n` + + `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="${publishableClientKeyFromArgs ?? ""}"\n` + "STACK_SECRET_SERVER_KEY=\n" : "# Stack Auth keys\n" + "# Get these variables by creating a project on https://app.stack-auth.com.\n" + - `NEXT_PUBLIC_STACK_PROJECT_ID=${projectIdFromArgs ?? ""}\n` + - `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${publishableClientKeyFromArgs ?? ""}\n` + + `NEXT_PUBLIC_STACK_PROJECT_ID="${projectIdFromArgs ?? ""}"\n` + + `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="${publishableClientKeyFromArgs ?? ""}"\n` + "STACK_SECRET_SERVER_KEY=\n"; laterWriteFile(envLocalPath, envContent);