Support create-next-app@15 with the setup wizard (#340)

This commit is contained in:
Konsti Wohlwend 2024-11-13 13:58:42 +01:00 committed by GitHub
parent 968802690f
commit c2bc80bda5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 99 additions and 69 deletions

View File

@ -12,7 +12,7 @@ export default function GlobalError({ error }: any) {
return (
<html>
<body suppressHydrationWarning>
<body>
[An unhandled error occurred.]
<Error
statusCode={500}

View File

@ -13,8 +13,8 @@ export default function RootLayout({
children: React.ReactNode,
}) {
return (
<html lang="en" suppressHydrationWarning>
<body suppressHydrationWarning>
<html lang="en">
<body>
{children}
</body>
</html>

View File

@ -26,7 +26,7 @@ export default function GlobalError({ error }: any) {
return (
<html>
<body suppressHydrationWarning>
<body>
{isProdLike ? (
<Spinner />
) : (

View File

@ -67,7 +67,7 @@ export default function RootLayout({
}
return (
<html lang="en" className={`${GeistSans.variable} ${GeistMono.variable}`} suppressHydrationWarning>
<html lang="en" className={`${GeistSans.variable} ${GeistMono.variable}`}>
<head>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<StyleLink href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded&display=block" />
@ -85,10 +85,9 @@ export default function RootLayout({
<CSPostHogProvider>
<body
className={cn(
"min-h-screen bg-background font-sans antialiased",
fontSans.variable
)}
suppressHydrationWarning
"min-h-screen bg-background font-sans antialiased",
fontSans.variable
)}
>
<Analytics />
<PageView />

View File

@ -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);
});

View File

@ -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);
}
}

View File

@ -19,7 +19,7 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<head />
<body suppressHydrationWarning>
<body>
<StackProvider app={stackServerApp}>
<StackTheme>
<Provider>

View File

@ -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}",

View File

@ -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);
} */
} */

View File

@ -20,7 +20,7 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning className={inter.className}>
<head />
<body suppressHydrationWarning>
<body>
<StackProvider
app={stackServerApp}
>

View File

@ -3,7 +3,7 @@ import { stackServerApp } from "../stack";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en">
<body className="bg-background text-foreground">
<StackProvider app={stackServerApp}>
<StackTheme>

1
packages/init-stack/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
test-run-output

View File

@ -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)) {

View File

@ -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",

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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)
);
};

View File

@ -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": {

View File

@ -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": {

View File

@ -30,7 +30,6 @@ export function MaybeFullPage({
<>
<div
id={id}
suppressHydrationWarning
style={{
minHeight: '100vh',
alignSelf: 'stretch',

View File

@ -11,7 +11,6 @@ export function SsrScript(props: { script: string, nonce?: string }) {
return (
<script
suppressHydrationWarning
nonce={props.nonce}
dangerouslySetInnerHTML={{ __html: props.script }}
/>

View File

@ -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({
<>
<BrowserScript nonce={nonce} />
<style
suppressHydrationWarning
nonce={nonce}
dangerouslySetInnerHTML={{
__html: globalCSS + "\n" + convertColorsToCSS(themeValue),

View File

@ -17,7 +17,14 @@ const script = () => {
};
const setTheme = (mode: 'dark' | 'light') => {
document.documentElement.setAttribute('data-stack-theme', mode);
let el = document.getElementById(`--stack-theme-mode`);
if (!el) {
el = document.createElement("style");
el.id = `--stack-theme-mode`;
el.innerHTML = `/* This tag is used by Stack Auth to set the theme in the browser without causing a hydration error (since React ignores additional tags in the <head>). We later use the \`html:has(head > [data-stack-theme=XYZ])\` selector to apply styles based on the theme. */`;
document.head.appendChild(el);
}
el.setAttribute("data-stack-theme", mode);
};
const colorToRGB = (color: string): [number, number, number] | null => {

View File

@ -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/**/*.{ts,tsx}",
"./node_modules/@stackframe/stack-ui/src/**/*.{ts,tsx}",

View File

@ -752,10 +752,10 @@ importers:
specifier: workspace:*
version: link:../stack-ui
'@types/react':
specifier: '>=18.2'
specifier: '>=18.2 || >=19.0.0-rc.0'
version: 18.3.12
'@types/react-dom':
specifier: '>=18.2'
specifier: '>=18.2 || >=19.0.0-rc.0'
version: 18.2.18
browser-image-compression:
specifier: ^2.0.2
@ -882,10 +882,10 @@ importers:
specifier: workspace:*
version: link:../stack-ui
'@types/react':
specifier: '>=18.2.66'
specifier: '>=18.2.66 || >=19.0.0-rc.0'
version: 18.3.12
'@types/react-dom':
specifier: '>=18.2.66'
specifier: '>=18.2.66 || >=19.0.0-rc.0'
version: 18.3.1
clsx:
specifier: ^2.1.1
@ -937,10 +937,10 @@ importers:
packages/stack-sc:
dependencies:
'@types/react':
specifier: '>=18.3.12'
specifier: '>=18.3.12 || >=19.0.0-rc.0'
version: 18.3.12
'@types/react-dom':
specifier: '>=18.3.1'
specifier: '>=18.3.1 || >=19.0.0-rc.0'
version: 18.3.1
devDependencies:
next:
@ -965,10 +965,10 @@ importers:
specifier: workspace:*
version: link:../stack-sc
'@types/react':
specifier: '>=18.2'
specifier: '>=18.2 || >=19.0.0-rc.0'
version: 18.3.12
'@types/react-dom':
specifier: '>=18.2'
specifier: '>=18.2 || >=19.0.0-rc.0'
version: 18.2.18
bcrypt:
specifier: ^5.1.1
@ -1116,10 +1116,10 @@ importers:
specifier: ^8.20.5
version: 8.20.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@types/react':
specifier: '>=18.2.12'
specifier: '>=18.2.12 || >=19.0.0-rc.0'
version: 18.3.12
'@types/react-dom':
specifier: '>=18.2.12'
specifier: '>=18.2.12 || >=19.0.0-rc.0'
version: 18.2.18
class-variance-authority:
specifier: ^0.7.0