diff --git a/apps/backend/src/app/global-error.tsx b/apps/backend/src/app/global-error.tsx index 55bb51bb7..d65c7f4d1 100644 --- a/apps/backend/src/app/global-error.tsx +++ b/apps/backend/src/app/global-error.tsx @@ -12,7 +12,7 @@ export default function GlobalError({ error }: any) { return ( - + [An unhandled error occurred.] - + + {children} diff --git a/apps/dashboard/src/app/global-error.tsx b/apps/dashboard/src/app/global-error.tsx index 60b99ce82..e43375bf6 100644 --- a/apps/dashboard/src/app/global-error.tsx +++ b/apps/dashboard/src/app/global-error.tsx @@ -26,7 +26,7 @@ export default function GlobalError({ error }: any) { return ( - + {isProdLike ? ( ) : ( diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx index 62beeec09..5d2d64b4f 100644 --- a/apps/dashboard/src/app/layout.tsx +++ b/apps/dashboard/src/app/layout.tsx @@ -67,7 +67,7 @@ export default function RootLayout({ } return ( - + @@ -85,10 +85,9 @@ export default function RootLayout({ diff --git a/apps/e2e/tests/general/setup-wizard.ts b/apps/e2e/tests/general/setup-wizard.ts new file mode 100644 index 000000000..4f57b5d9a --- /dev/null +++ b/apps/e2e/tests/general/setup-wizard.ts @@ -0,0 +1,14 @@ +import { exec } from "child_process"; +import { describe } from "vitest"; +import { it } from "../helpers"; + +describe("Setup wizard", () => { + it("completes successfully with create-next-app", async ({ expect }) => { + const [error, stdout, stderr] = await new Promise<[Error | null, string, string]>((resolve) => { + exec("pnpm -C packages/init-stack run test-run-auto", (error, stdout, stderr) => { + resolve([error, stdout, stderr]); + }); + }); + expect(error, `Expected no error to be thrown!\n\n\n\nstdout: ${stdout}\n\n\n\nstderr: ${stderr}`).toBeNull(); + }, 120_000); +}); diff --git a/examples/demo/src/app/global.css b/examples/demo/src/app/global.css index 7a6c5b0a8..b8ee13918 100644 --- a/examples/demo/src/app/global.css +++ b/examples/demo/src/app/global.css @@ -8,7 +8,7 @@ --foreground: black; } -[data-stack-theme='dark'] { +html:has(head > [data-stack-theme="dark"]) { --background: black; --foreground: white; } @@ -16,4 +16,4 @@ body { background-color: var(--background); color: var(--foreground); -} \ No newline at end of file +} diff --git a/examples/demo/src/app/layout.tsx b/examples/demo/src/app/layout.tsx index abd3607f9..4a9d6a036 100644 --- a/examples/demo/src/app/layout.tsx +++ b/examples/demo/src/app/layout.tsx @@ -19,7 +19,7 @@ export default function RootLayout({ return ( - + diff --git a/examples/demo/tailwind.config.js b/examples/demo/tailwind.config.js index a2e4a0264..1141cd681 100644 --- a/examples/demo/tailwind.config.js +++ b/examples/demo/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: ["selector", '[data-stack-theme="dark"]'], + darkMode: ["selector", 'html:has(head > [data-stack-theme="dark"])'], content: [ "./src/**/*.{js,ts,jsx,tsx,mdx}", "./node_modules/@stackframe/stack-ui/src/**/*.{ts,tsx}", diff --git a/examples/docs-examples/src/app/global.css b/examples/docs-examples/src/app/global.css index ffa369697..fc0fd0078 100644 --- a/examples/docs-examples/src/app/global.css +++ b/examples/docs-examples/src/app/global.css @@ -8,7 +8,7 @@ --foreground: black; } -[data-stack-theme='dark'] { +html:has(head > [data-stack-theme="dark"]) { --background: black; --foreground: white; } @@ -16,4 +16,4 @@ body { background-color: var(--background); color: var(--foreground); -} */ \ No newline at end of file +} */ diff --git a/examples/docs-examples/src/app/layout.tsx b/examples/docs-examples/src/app/layout.tsx index 472bc8add..66c43a4c3 100644 --- a/examples/docs-examples/src/app/layout.tsx +++ b/examples/docs-examples/src/app/layout.tsx @@ -20,7 +20,7 @@ export default function RootLayout({ return ( - + diff --git a/examples/supabase/app/layout.tsx b/examples/supabase/app/layout.tsx index c70f9deb0..92e05551f 100644 --- a/examples/supabase/app/layout.tsx +++ b/examples/supabase/app/layout.tsx @@ -3,7 +3,7 @@ import { stackServerApp } from "../stack"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + diff --git a/packages/init-stack/.gitignore b/packages/init-stack/.gitignore new file mode 100644 index 000000000..a488bad01 --- /dev/null +++ b/packages/init-stack/.gitignore @@ -0,0 +1 @@ +test-run-output diff --git a/packages/init-stack/index.mjs b/packages/init-stack/index.mjs index e5bac5968..cf4dbbdde 100644 --- a/packages/init-stack/index.mjs +++ b/packages/init-stack/index.mjs @@ -75,7 +75,7 @@ async function main() { ); } const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); - const nextVersionInPackageJson = packageJson?.dependencies["next"] ?? packageJson?.devDependencies["next"]; + const nextVersionInPackageJson = packageJson?.dependencies?.["next"] ?? packageJson?.devDependencies?.["next"]; if (!nextVersionInPackageJson) { throw new UserError( `The project at ${projectPath} does not appear to be a Next.js project, or does not have 'next' installed as a dependency. Only Next.js projects are currently supported.` @@ -195,6 +195,8 @@ async function main() { ); } + const stackPackageName = process.env.STACK_PACKAGE_NAME_OVERRIDE || "@stackframe/stack"; + const isReady = await inquirer.prompt([ { type: "confirm", @@ -209,7 +211,7 @@ async function main() { console.log(); console.log(`${ansis.bold}Installing dependencies...${ansis.clear}`); - await shellNicelyFormatted(`${installCommand} @stackframe/stack`, { + await shellNicelyFormatted(`${installCommand} ${stackPackageName}`, { shell: true, cwd: projectPath, }); @@ -294,7 +296,9 @@ main() "For more information, please visit https://docs.stack-auth.com/getting-started/setup" ); console.log(); - await open("https://app.stack-auth.com/wizard-congrats"); + if (!process.env.STACK_DISABLE_INTERACTIVE) { + await open("https://app.stack-auth.com/wizard-congrats"); + } }) .catch((err) => { if (!(err instanceof UserError)) { diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json index afd66ab31..9cd3b8291 100644 --- a/packages/init-stack/package.json +++ b/packages/init-stack/package.json @@ -5,7 +5,11 @@ "main": "index.mjs", "bin": "./index.mjs", "scripts": { - "init-stack": "node index.mjs" + "clean": "rimraf test-run-output && rimraf node_modules", + "init-stack": "node index.mjs", + "init-stack:local": "STACK_PACKAGE_NAME_OVERRIDE=../../stack node index.mjs", + "test-run": "rimraf test-run-output && npx -y create-next-app@latest test-run-output && pnpm run init-stack:local test-run-output", + "test-run-auto": "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" }, "files": [ "README.md", diff --git a/packages/stack-emails/package.json b/packages/stack-emails/package.json index f717c3ff6..ba559d133 100644 --- a/packages/stack-emails/package.json +++ b/packages/stack-emails/package.json @@ -28,12 +28,12 @@ } }, "peerDependencies": { - "react": ">=18.2", - "react-dom": ">=18.2", + "react": ">=18.2 || >=19.0.0-rc.0", + "react-dom": ">=18.2 || >=19.0.0-rc.0", "yup": "^1.4.0", - "@types/react": ">=18.2.66", - "@types/react-dom": ">=18.2.66", - "next": ">=14.1.0" + "@types/react": ">=18.2.66 || >=19.0.0-rc.0", + "@types/react-dom": ">=18.2.66 || >=19.0.0-rc.0", + "next": ">=14.1.0 || >=15.0.0-rc.0" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/stack-sc/package.json b/packages/stack-sc/package.json index c5de56bba..8e6011c02 100644 --- a/packages/stack-sc/package.json +++ b/packages/stack-sc/package.json @@ -30,11 +30,11 @@ "LICENSE" ], "peerDependencies": { - "next": ">=14.1", - "react": ">=18.2", - "react-dom": ">=18.2", - "@types/react": ">=18.3.12", - "@types/react-dom": ">=18.3.1" + "next": ">=14.1 || >=15.0.0-rc.0", + "react": ">=18.2 || >=19.0.0-rc.0", + "react-dom": ">=18.2 || >=19.0.0-rc.0", + "@types/react": ">=18.3.12 || >=19.0.0-rc.0", + "@types/react-dom": ">=18.3.1 || >=19.0.0-rc.0" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json index cbc013f09..961714461 100644 --- a/packages/stack-shared/package.json +++ b/packages/stack-shared/package.json @@ -27,11 +27,11 @@ } }, "peerDependencies": { - "react": ">=18.2", - "react-dom": ">=18.2", - "@types/react": ">=18.2", - "@types/react-dom": ">=18.2", - "next": ">=14.1.0", + "react": ">=18.2 || >=19.0.0-rc.0", + "react-dom": ">=18.2 || >=19.0.0-rc.0", + "@types/react": ">=18.2 || >=19.0.0-rc.0", + "@types/react-dom": ">=18.2 || >=19.0.0-rc.0", + "next": ">=14.1.0 || >=15.0.0-rc.0", "yup": "^1.4.0" }, "peerDependenciesMeta": { diff --git a/packages/stack-shared/src/hooks/use-hash.tsx b/packages/stack-shared/src/hooks/use-hash.tsx index e5868ccb8..fc82b6851 100644 --- a/packages/stack-shared/src/hooks/use-hash.tsx +++ b/packages/stack-shared/src/hooks/use-hash.tsx @@ -1,10 +1,14 @@ -import { useParams } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useSyncExternalStore } from "react"; +import { suspendIfSsr } from "../utils/react"; -const getHash = () => typeof window === "undefined" ? undefined : window.location.hash.substring(1); export const useHash = () => { - const params = useParams(); - const [hash, setHash] = useState(getHash()); - useEffect(() => setHash(getHash()), [params]); - return hash; + suspendIfSsr("useHash"); + return useSyncExternalStore( + (onChange) => { + const interval = setInterval(() => onChange(), 10); + return () => clearInterval(interval); + }, + () => window.location.hash.substring(1) + ); }; + diff --git a/packages/stack-ui/package.json b/packages/stack-ui/package.json index 68701a663..59d697b49 100644 --- a/packages/stack-ui/package.json +++ b/packages/stack-ui/package.json @@ -23,11 +23,11 @@ } }, "peerDependencies": { - "react": ">=18.2", - "react-dom": ">=18.2", - "@types/react": ">=18.2.12", - "@types/react-dom": ">=18.2.12", - "next": ">=14.1.0", + "react": ">=18.2 || >=19.0.0-rc.0", + "react-dom": ">=18.2 || >=19.0.0-rc.0", + "@types/react": ">=18.2.12 || >=19.0.0-rc.0", + "@types/react-dom": ">=18.2.12 || >=19.0.0-rc.0", + "next": ">=14.1.0 || >=15.0.0-rc.0", "yup": "^1.4.0" }, "peerDependenciesMeta": { diff --git a/packages/stack/package.json b/packages/stack/package.json index 7ebc5a74a..12ab819b8 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -66,11 +66,11 @@ "yup": "^1.4.0" }, "peerDependencies": { - "next": ">=14.1", - "react": ">=18.2", - "react-dom": ">=18.2", - "@types/react": ">=18.2", - "@types/react-dom": ">=18.2" + "next": ">=14.1 || >=15.0.0-rc.0", + "react": ">=18.2 || >=19.0.0-rc.0", + "react-dom": ">=18.2 || >=19.0.0-rc.0", + "@types/react": ">=18.2 || >=19.0.0-rc.0", + "@types/react-dom": ">=18.2 || >=19.0.0-rc.0" }, "peerDependenciesMeta": { "@types/react": { diff --git a/packages/stack/src/components/elements/maybe-full-page.tsx b/packages/stack/src/components/elements/maybe-full-page.tsx index 2999931ee..b4daf3be9 100644 --- a/packages/stack/src/components/elements/maybe-full-page.tsx +++ b/packages/stack/src/components/elements/maybe-full-page.tsx @@ -30,7 +30,6 @@ export function MaybeFullPage({ <>
diff --git a/packages/stack/src/providers/theme-provider.tsx b/packages/stack/src/providers/theme-provider.tsx index 9b0c2e1c2..a9145e24d 100644 --- a/packages/stack/src/providers/theme-provider.tsx +++ b/packages/stack/src/providers/theme-provider.tsx @@ -69,7 +69,7 @@ function convertColorsToCSS(theme: Theme) { .stack-scope { ${colorsToCSSVars(colors.light)} } - [data-stack-theme="dark"] .stack-scope { + html:has(head > [data-stack-theme="dark"]) .stack-scope { ${colorsToCSSVars(colors.dark)} }`; } @@ -95,7 +95,6 @@ export function StackTheme({ <>