diff --git a/.changeset/pre.json b/.changeset/pre.json index 5ce3fb4364..fe1148c3d5 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -35,7 +35,6 @@ }, "changesets": [ "crazy-seas-eat", - "dirty-needles-chew", "great-parrots-yell", "mighty-pigs-add", "stupid-ghosts-decide", diff --git a/.changeset/puny-chicken-argue.md b/.changeset/puny-chicken-argue.md new file mode 100644 index 0000000000..cfd78a07a2 --- /dev/null +++ b/.changeset/puny-chicken-argue.md @@ -0,0 +1,5 @@ +--- +"react-email": minor +--- + +use a separate package for storing the preview server (@react-email/preview-server) diff --git a/apps/demo/package.json b/apps/demo/package.json index a493d3a061..b886a9659b 100644 --- a/apps/demo/package.json +++ b/apps/demo/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "build": "email build", + "build": "pnpm install --frozen-lockfile && email build", "dev": "email dev", "start": "email start", "export": "email export" @@ -15,7 +15,8 @@ "react-email": "workspace:*" }, "devDependencies": { - "next": "15.3.1", + "@react-email/preview-server": "workspace:*", + "next": "^15.2.4", "@types/react": "^19", "@types/react-dom": "^19", "tsx": "4.19.3" diff --git a/biome.json b/biome.json index 21871acfbb..8f3beaf4e1 100644 --- a/biome.json +++ b/biome.json @@ -87,7 +87,7 @@ "!**/pnpm-lock.yaml", "!**/.next", "!**/public", - "!packages/react-email/src/actions/email-validation/caniemail-data.ts", + "!packages/preview-server/src/actions/email-validation/caniemail-data.ts", "!**/.react-email/**/*", "!**/node_modules/**/*", "!**/*.d.ts", diff --git a/packages/preview-server/.gitignore b/packages/preview-server/.gitignore new file mode 100644 index 0000000000..659fa846ed --- /dev/null +++ b/packages/preview-server/.gitignore @@ -0,0 +1,5 @@ +node_modules +.next + +# for testing +static diff --git a/packages/preview-server/.npmignore b/packages/preview-server/.npmignore new file mode 100644 index 0000000000..365b417e3c --- /dev/null +++ b/packages/preview-server/.npmignore @@ -0,0 +1,5 @@ +.react-email +./emails +./emails/static +node_modules +.turbo diff --git a/packages/preview-server/_index.js b/packages/preview-server/_index.js new file mode 100644 index 0000000000..e3eb1b23d6 --- /dev/null +++ b/packages/preview-server/_index.js @@ -0,0 +1,4 @@ +/** + * this file is just a placeholder file so that import.meta.resolve and require.resolve can properly + * find out the path to this module. This file does not do anything nor does it need to export anything of value. + */ diff --git a/packages/preview-server/license.md b/packages/preview-server/license.md new file mode 100644 index 0000000000..5058f53ebb --- /dev/null +++ b/packages/preview-server/license.md @@ -0,0 +1,7 @@ +Copyright 2024 Plus Five Five, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/react-email/module-punycode.d.ts b/packages/preview-server/module-punycode.d.ts similarity index 100% rename from packages/react-email/module-punycode.d.ts rename to packages/preview-server/module-punycode.d.ts diff --git a/packages/react-email/next-env.d.ts b/packages/preview-server/next-env.d.ts similarity index 100% rename from packages/react-email/next-env.d.ts rename to packages/preview-server/next-env.d.ts diff --git a/packages/react-email/next.config.js b/packages/preview-server/next.config.js similarity index 100% rename from packages/react-email/next.config.js rename to packages/preview-server/next.config.js diff --git a/packages/preview-server/package.json b/packages/preview-server/package.json new file mode 100644 index 0000000000..e57564649b --- /dev/null +++ b/packages/preview-server/package.json @@ -0,0 +1,82 @@ +{ + "name": "@react-email/preview-server", + "version": "1.0.0-canary.0", + "description": "A live preview of your emails right in your browser.", + "scripts": { + "caniemail:fetch": "node ./scripts/fill-caniemail-data.mjs", + "clean": "rm -rf dist", + "build": "node ./scripts/build-preview-server.mjs", + "test": "vitest run", + "test:watch": "vitest" + }, + "main": "./_index.js", + "dependencies": { + "@babel/core": "7.26.10", + "@babel/parser": "^7.27.0", + "@babel/traverse": "^7.27.0", + "@lottiefiles/dotlottie-react": "0.13.3", + "@radix-ui/colors": "3.0.0", + "@radix-ui/react-collapsible": "1.1.7", + "@radix-ui/react-dropdown-menu": "2.1.10", + "@radix-ui/react-popover": "1.1.10", + "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-tabs": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.6", + "@radix-ui/react-tooltip": "1.2.3", + "@types/node": "22.14.1", + "@types/normalize-path": "3.0.2", + "@types/react": "19.0.10", + "@types/react-dom": "19.0.4", + "@types/webpack": "5.28.5", + "autoprefixer": "10.4.21", + "chalk": "^4.1.2", + "clsx": "2.1.1", + "esbuild": "^0.25.0", + "framer-motion": "12.7.5", + "json5": "2.2.3", + "log-symbols": "^4.1.0", + "module-punycode": "npm:punycode@2.3.1", + "next": "^15.2.4", + "node-html-parser": "7.0.1", + "ora": "^5.4.1", + "pretty-bytes": "6.1.1", + "prism-react-renderer": "2.4.1", + "react": "19.0.0", + "react-dom": "19.0.0", + "sharp": "0.34.1", + "socket.io-client": "4.8.1", + "sonner": "2.0.3", + "source-map-js": "1.2.1", + "spamc": "0.0.5", + "stacktrace-parser": "0.1.11", + "tailwind-merge": "3.2.0", + "tailwindcss": "3.4.0", + "use-debounce": "10.0.4", + "zod": "3.24.3" + }, + "devDependencies": { + "@react-email/components": "workspace:*", + "@types/babel__core": "7.20.5", + "@types/babel__traverse": "7.20.7", + "@types/fs-extra": "11.0.1", + "@types/mime-types": "2.1.4", + "@types/node": "22.10.2", + "@types/normalize-path": "3.0.2", + "@types/react": "19.0.10", + "@types/react-dom": "19.0.4", + "@types/webpack": "5.28.5", + "autoprefixer": "10.4.21", + "postcss": "8.5.3", + "tailwindcss": "3.4.0", + "typescript": "5.8.3" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/resend/react-email.git", + "directory": "packages/preview-server" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/react-email/postcss.config.js b/packages/preview-server/postcss.config.js similarity index 100% rename from packages/react-email/postcss.config.js rename to packages/preview-server/postcss.config.js diff --git a/packages/preview-server/scripts/build-preview-server.mjs b/packages/preview-server/scripts/build-preview-server.mjs new file mode 100644 index 0000000000..36c0236651 --- /dev/null +++ b/packages/preview-server/scripts/build-preview-server.mjs @@ -0,0 +1,29 @@ +import { spawn } from 'node:child_process'; +import fs from 'node:fs'; +import path from 'node:path'; +import url from 'node:url'; + +const filename = url.fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +const nextBuildProcess = spawn('pnpm', ['next', 'build'], { + detached: true, + shell: true, + stdio: 'inherit', + cwd: path.resolve(dirname, '../'), +}); + +process.on('SIGINT', () => { + nextBuildProcess.kill('SIGINT'); +}); + +nextBuildProcess.on('exit', (code) => { + if (code !== 0) { + console.error(`next build failed with exit code ${code}`); + process.exit(code); + } + + fs.rmSync(path.resolve(dirname, '../.next/cache'), { + recursive: true, + }); +}); diff --git a/packages/react-email/scripts/fill-caniemail-data.mjs b/packages/preview-server/scripts/fill-caniemail-data.mjs similarity index 100% rename from packages/react-email/scripts/fill-caniemail-data.mjs rename to packages/preview-server/scripts/fill-caniemail-data.mjs diff --git a/packages/react-email/src/actions/email-validation/caniemail-data.ts b/packages/preview-server/src/actions/email-validation/caniemail-data.ts similarity index 100% rename from packages/react-email/src/actions/email-validation/caniemail-data.ts rename to packages/preview-server/src/actions/email-validation/caniemail-data.ts diff --git a/packages/react-email/src/actions/email-validation/check-compatibility.ts b/packages/preview-server/src/actions/email-validation/check-compatibility.ts similarity index 100% rename from packages/react-email/src/actions/email-validation/check-compatibility.ts rename to packages/preview-server/src/actions/email-validation/check-compatibility.ts diff --git a/packages/react-email/src/actions/email-validation/check-images.spec.tsx b/packages/preview-server/src/actions/email-validation/check-images.spec.tsx similarity index 100% rename from packages/react-email/src/actions/email-validation/check-images.spec.tsx rename to packages/preview-server/src/actions/email-validation/check-images.spec.tsx diff --git a/packages/react-email/src/actions/email-validation/check-images.ts b/packages/preview-server/src/actions/email-validation/check-images.ts similarity index 100% rename from packages/react-email/src/actions/email-validation/check-images.ts rename to packages/preview-server/src/actions/email-validation/check-images.ts diff --git a/packages/react-email/src/actions/email-validation/check-links.spec.tsx b/packages/preview-server/src/actions/email-validation/check-links.spec.tsx similarity index 100% rename from packages/react-email/src/actions/email-validation/check-links.spec.tsx rename to packages/preview-server/src/actions/email-validation/check-links.spec.tsx diff --git a/packages/react-email/src/actions/email-validation/check-links.ts b/packages/preview-server/src/actions/email-validation/check-links.ts similarity index 100% rename from packages/react-email/src/actions/email-validation/check-links.ts rename to packages/preview-server/src/actions/email-validation/check-links.ts diff --git a/packages/react-email/src/actions/email-validation/get-code-location-from-ast-element.ts b/packages/preview-server/src/actions/email-validation/get-code-location-from-ast-element.ts similarity index 100% rename from packages/react-email/src/actions/email-validation/get-code-location-from-ast-element.ts rename to packages/preview-server/src/actions/email-validation/get-code-location-from-ast-element.ts diff --git a/packages/react-email/src/actions/email-validation/quick-fetch.ts b/packages/preview-server/src/actions/email-validation/quick-fetch.ts similarity index 100% rename from packages/react-email/src/actions/email-validation/quick-fetch.ts rename to packages/preview-server/src/actions/email-validation/quick-fetch.ts diff --git a/packages/react-email/src/actions/get-email-path-from-slug.ts b/packages/preview-server/src/actions/get-email-path-from-slug.ts similarity index 100% rename from packages/react-email/src/actions/get-email-path-from-slug.ts rename to packages/preview-server/src/actions/get-email-path-from-slug.ts diff --git a/packages/react-email/src/actions/get-emails-directory-metadata-action.ts b/packages/preview-server/src/actions/get-emails-directory-metadata-action.ts similarity index 100% rename from packages/react-email/src/actions/get-emails-directory-metadata-action.ts rename to packages/preview-server/src/actions/get-emails-directory-metadata-action.ts diff --git a/packages/react-email/src/actions/render-email-by-path.tsx b/packages/preview-server/src/actions/render-email-by-path.tsx similarity index 100% rename from packages/react-email/src/actions/render-email-by-path.tsx rename to packages/preview-server/src/actions/render-email-by-path.tsx diff --git a/packages/react-email/src/animated-icons-data/help.json b/packages/preview-server/src/animated-icons-data/help.json similarity index 100% rename from packages/react-email/src/animated-icons-data/help.json rename to packages/preview-server/src/animated-icons-data/help.json diff --git a/packages/react-email/src/animated-icons-data/link.json b/packages/preview-server/src/animated-icons-data/link.json similarity index 100% rename from packages/react-email/src/animated-icons-data/link.json rename to packages/preview-server/src/animated-icons-data/link.json diff --git a/packages/react-email/src/animated-icons-data/load.json b/packages/preview-server/src/animated-icons-data/load.json similarity index 100% rename from packages/react-email/src/animated-icons-data/load.json rename to packages/preview-server/src/animated-icons-data/load.json diff --git a/packages/react-email/src/animated-icons-data/mail.json b/packages/preview-server/src/animated-icons-data/mail.json similarity index 100% rename from packages/react-email/src/animated-icons-data/mail.json rename to packages/preview-server/src/animated-icons-data/mail.json diff --git a/packages/react-email/src/app/env.ts b/packages/preview-server/src/app/env.ts similarity index 100% rename from packages/react-email/src/app/env.ts rename to packages/preview-server/src/app/env.ts diff --git a/packages/react-email/src/app/favicon.ico b/packages/preview-server/src/app/favicon.ico similarity index 100% rename from packages/react-email/src/app/favicon.ico rename to packages/preview-server/src/app/favicon.ico diff --git a/packages/react-email/src/app/fonts.ts b/packages/preview-server/src/app/fonts.ts similarity index 100% rename from packages/react-email/src/app/fonts.ts rename to packages/preview-server/src/app/fonts.ts diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoBold.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoBold.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoBold.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoBold.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoBoldItalic.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoBoldItalic.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoBoldItalic.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoBoldItalic.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoHeavy.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoHeavy.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoHeavy.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoHeavy.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoHeavyItalic.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoHeavyItalic.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoHeavyItalic.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoHeavyItalic.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoLight.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoLight.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoLight.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoLight.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoLightItalic.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoLightItalic.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoLightItalic.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoLightItalic.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoMedium.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoMedium.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoMedium.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoMedium.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoMediumItalic.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoMediumItalic.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoMediumItalic.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoMediumItalic.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoRegular.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoRegular.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoRegular.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoRegular.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoRegularItalic.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoRegularItalic.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoRegularItalic.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoRegularItalic.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoSemibold.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoSemibold.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoSemibold.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoSemibold.otf diff --git a/packages/react-email/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf b/packages/preview-server/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf similarity index 100% rename from packages/react-email/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf rename to packages/preview-server/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf diff --git a/packages/react-email/src/app/globals.css b/packages/preview-server/src/app/globals.css similarity index 100% rename from packages/react-email/src/app/globals.css rename to packages/preview-server/src/app/globals.css diff --git a/packages/react-email/src/app/layout.tsx b/packages/preview-server/src/app/layout.tsx similarity index 100% rename from packages/react-email/src/app/layout.tsx rename to packages/preview-server/src/app/layout.tsx diff --git a/packages/react-email/src/app/logo.png b/packages/preview-server/src/app/logo.png similarity index 100% rename from packages/react-email/src/app/logo.png rename to packages/preview-server/src/app/logo.png diff --git a/packages/react-email/src/app/page.tsx b/packages/preview-server/src/app/page.tsx similarity index 100% rename from packages/react-email/src/app/page.tsx rename to packages/preview-server/src/app/page.tsx diff --git a/packages/react-email/src/app/preview/[...slug]/page.tsx b/packages/preview-server/src/app/preview/[...slug]/page.tsx similarity index 100% rename from packages/react-email/src/app/preview/[...slug]/page.tsx rename to packages/preview-server/src/app/preview/[...slug]/page.tsx diff --git a/packages/react-email/src/app/preview/[...slug]/preview.tsx b/packages/preview-server/src/app/preview/[...slug]/preview.tsx similarity index 100% rename from packages/react-email/src/app/preview/[...slug]/preview.tsx rename to packages/preview-server/src/app/preview/[...slug]/preview.tsx diff --git a/packages/react-email/src/app/preview/[...slug]/rendering-error.tsx b/packages/preview-server/src/app/preview/[...slug]/rendering-error.tsx similarity index 100% rename from packages/react-email/src/app/preview/[...slug]/rendering-error.tsx rename to packages/preview-server/src/app/preview/[...slug]/rendering-error.tsx diff --git a/packages/react-email/src/components/button.tsx b/packages/preview-server/src/components/button.tsx similarity index 100% rename from packages/react-email/src/components/button.tsx rename to packages/preview-server/src/components/button.tsx diff --git a/packages/react-email/src/components/code-container.tsx b/packages/preview-server/src/components/code-container.tsx similarity index 100% rename from packages/react-email/src/components/code-container.tsx rename to packages/preview-server/src/components/code-container.tsx diff --git a/packages/react-email/src/components/code-snippet.tsx b/packages/preview-server/src/components/code-snippet.tsx similarity index 100% rename from packages/react-email/src/components/code-snippet.tsx rename to packages/preview-server/src/components/code-snippet.tsx diff --git a/packages/react-email/src/components/code.tsx b/packages/preview-server/src/components/code.tsx similarity index 100% rename from packages/react-email/src/components/code.tsx rename to packages/preview-server/src/components/code.tsx diff --git a/packages/react-email/src/components/heading.tsx b/packages/preview-server/src/components/heading.tsx similarity index 100% rename from packages/react-email/src/components/heading.tsx rename to packages/preview-server/src/components/heading.tsx diff --git a/packages/react-email/src/components/icons/icon-arrow-down.tsx b/packages/preview-server/src/components/icons/icon-arrow-down.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-arrow-down.tsx rename to packages/preview-server/src/components/icons/icon-arrow-down.tsx diff --git a/packages/react-email/src/components/icons/icon-base.tsx b/packages/preview-server/src/components/icons/icon-base.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-base.tsx rename to packages/preview-server/src/components/icons/icon-base.tsx diff --git a/packages/react-email/src/components/icons/icon-bug.tsx b/packages/preview-server/src/components/icons/icon-bug.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-bug.tsx rename to packages/preview-server/src/components/icons/icon-bug.tsx diff --git a/packages/react-email/src/components/icons/icon-button.tsx b/packages/preview-server/src/components/icons/icon-button.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-button.tsx rename to packages/preview-server/src/components/icons/icon-button.tsx diff --git a/packages/react-email/src/components/icons/icon-check.tsx b/packages/preview-server/src/components/icons/icon-check.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-check.tsx rename to packages/preview-server/src/components/icons/icon-check.tsx diff --git a/packages/react-email/src/components/icons/icon-clipboard.tsx b/packages/preview-server/src/components/icons/icon-clipboard.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-clipboard.tsx rename to packages/preview-server/src/components/icons/icon-clipboard.tsx diff --git a/packages/react-email/src/components/icons/icon-download.tsx b/packages/preview-server/src/components/icons/icon-download.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-download.tsx rename to packages/preview-server/src/components/icons/icon-download.tsx diff --git a/packages/react-email/src/components/icons/icon-email.tsx b/packages/preview-server/src/components/icons/icon-email.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-email.tsx rename to packages/preview-server/src/components/icons/icon-email.tsx diff --git a/packages/react-email/src/components/icons/icon-file.tsx b/packages/preview-server/src/components/icons/icon-file.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-file.tsx rename to packages/preview-server/src/components/icons/icon-file.tsx diff --git a/packages/react-email/src/components/icons/icon-folder-open.tsx b/packages/preview-server/src/components/icons/icon-folder-open.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-folder-open.tsx rename to packages/preview-server/src/components/icons/icon-folder-open.tsx diff --git a/packages/react-email/src/components/icons/icon-folder.tsx b/packages/preview-server/src/components/icons/icon-folder.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-folder.tsx rename to packages/preview-server/src/components/icons/icon-folder.tsx diff --git a/packages/react-email/src/components/icons/icon-hide-sidebar.tsx b/packages/preview-server/src/components/icons/icon-hide-sidebar.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-hide-sidebar.tsx rename to packages/preview-server/src/components/icons/icon-hide-sidebar.tsx diff --git a/packages/react-email/src/components/icons/icon-image.tsx b/packages/preview-server/src/components/icons/icon-image.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-image.tsx rename to packages/preview-server/src/components/icons/icon-image.tsx diff --git a/packages/react-email/src/components/icons/icon-info.tsx b/packages/preview-server/src/components/icons/icon-info.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-info.tsx rename to packages/preview-server/src/components/icons/icon-info.tsx diff --git a/packages/react-email/src/components/icons/icon-link.tsx b/packages/preview-server/src/components/icons/icon-link.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-link.tsx rename to packages/preview-server/src/components/icons/icon-link.tsx diff --git a/packages/react-email/src/components/icons/icon-monitor.tsx b/packages/preview-server/src/components/icons/icon-monitor.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-monitor.tsx rename to packages/preview-server/src/components/icons/icon-monitor.tsx diff --git a/packages/react-email/src/components/icons/icon-phone.tsx b/packages/preview-server/src/components/icons/icon-phone.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-phone.tsx rename to packages/preview-server/src/components/icons/icon-phone.tsx diff --git a/packages/react-email/src/components/icons/icon-reload.tsx b/packages/preview-server/src/components/icons/icon-reload.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-reload.tsx rename to packages/preview-server/src/components/icons/icon-reload.tsx diff --git a/packages/react-email/src/components/icons/icon-source.tsx b/packages/preview-server/src/components/icons/icon-source.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-source.tsx rename to packages/preview-server/src/components/icons/icon-source.tsx diff --git a/packages/react-email/src/components/icons/icon-stamp.tsx b/packages/preview-server/src/components/icons/icon-stamp.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-stamp.tsx rename to packages/preview-server/src/components/icons/icon-stamp.tsx diff --git a/packages/react-email/src/components/icons/icon-warning.tsx b/packages/preview-server/src/components/icons/icon-warning.tsx similarity index 100% rename from packages/react-email/src/components/icons/icon-warning.tsx rename to packages/preview-server/src/components/icons/icon-warning.tsx diff --git a/packages/react-email/src/components/index.ts b/packages/preview-server/src/components/index.ts similarity index 100% rename from packages/react-email/src/components/index.ts rename to packages/preview-server/src/components/index.ts diff --git a/packages/react-email/src/components/logo.tsx b/packages/preview-server/src/components/logo.tsx similarity index 100% rename from packages/react-email/src/components/logo.tsx rename to packages/preview-server/src/components/logo.tsx diff --git a/packages/react-email/src/components/resizable-wrapper.tsx b/packages/preview-server/src/components/resizable-wrapper.tsx similarity index 100% rename from packages/react-email/src/components/resizable-wrapper.tsx rename to packages/preview-server/src/components/resizable-wrapper.tsx diff --git a/packages/react-email/src/components/send.tsx b/packages/preview-server/src/components/send.tsx similarity index 100% rename from packages/react-email/src/components/send.tsx rename to packages/preview-server/src/components/send.tsx diff --git a/packages/react-email/src/components/shell.tsx b/packages/preview-server/src/components/shell.tsx similarity index 100% rename from packages/react-email/src/components/shell.tsx rename to packages/preview-server/src/components/shell.tsx diff --git a/packages/react-email/src/components/sidebar/file-tree-directory-children.tsx b/packages/preview-server/src/components/sidebar/file-tree-directory-children.tsx similarity index 100% rename from packages/react-email/src/components/sidebar/file-tree-directory-children.tsx rename to packages/preview-server/src/components/sidebar/file-tree-directory-children.tsx diff --git a/packages/react-email/src/components/sidebar/file-tree-directory.tsx b/packages/preview-server/src/components/sidebar/file-tree-directory.tsx similarity index 100% rename from packages/react-email/src/components/sidebar/file-tree-directory.tsx rename to packages/preview-server/src/components/sidebar/file-tree-directory.tsx diff --git a/packages/react-email/src/components/sidebar/file-tree.tsx b/packages/preview-server/src/components/sidebar/file-tree.tsx similarity index 100% rename from packages/react-email/src/components/sidebar/file-tree.tsx rename to packages/preview-server/src/components/sidebar/file-tree.tsx diff --git a/packages/react-email/src/components/sidebar/index.ts b/packages/preview-server/src/components/sidebar/index.ts similarity index 100% rename from packages/react-email/src/components/sidebar/index.ts rename to packages/preview-server/src/components/sidebar/index.ts diff --git a/packages/react-email/src/components/sidebar/sidebar.tsx b/packages/preview-server/src/components/sidebar/sidebar.tsx similarity index 100% rename from packages/react-email/src/components/sidebar/sidebar.tsx rename to packages/preview-server/src/components/sidebar/sidebar.tsx diff --git a/packages/react-email/src/components/text.tsx b/packages/preview-server/src/components/text.tsx similarity index 100% rename from packages/react-email/src/components/text.tsx rename to packages/preview-server/src/components/text.tsx diff --git a/packages/react-email/src/components/toolbar.tsx b/packages/preview-server/src/components/toolbar.tsx similarity index 100% rename from packages/react-email/src/components/toolbar.tsx rename to packages/preview-server/src/components/toolbar.tsx diff --git a/packages/react-email/src/components/toolbar/checking-results.tsx b/packages/preview-server/src/components/toolbar/checking-results.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/checking-results.tsx rename to packages/preview-server/src/components/toolbar/checking-results.tsx diff --git a/packages/react-email/src/components/toolbar/code-preview-line-link.tsx b/packages/preview-server/src/components/toolbar/code-preview-line-link.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/code-preview-line-link.tsx rename to packages/preview-server/src/components/toolbar/code-preview-line-link.tsx diff --git a/packages/react-email/src/components/toolbar/compatibility.tsx b/packages/preview-server/src/components/toolbar/compatibility.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/compatibility.tsx rename to packages/preview-server/src/components/toolbar/compatibility.tsx diff --git a/packages/react-email/src/components/toolbar/linter.tsx b/packages/preview-server/src/components/toolbar/linter.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/linter.tsx rename to packages/preview-server/src/components/toolbar/linter.tsx diff --git a/packages/react-email/src/components/toolbar/results-table.tsx b/packages/preview-server/src/components/toolbar/results-table.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/results-table.tsx rename to packages/preview-server/src/components/toolbar/results-table.tsx diff --git a/packages/react-email/src/components/toolbar/results.tsx b/packages/preview-server/src/components/toolbar/results.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/results.tsx rename to packages/preview-server/src/components/toolbar/results.tsx diff --git a/packages/react-email/src/components/toolbar/spam-assassin.tsx b/packages/preview-server/src/components/toolbar/spam-assassin.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/spam-assassin.tsx rename to packages/preview-server/src/components/toolbar/spam-assassin.tsx diff --git a/packages/react-email/src/components/toolbar/toolbar-button.tsx b/packages/preview-server/src/components/toolbar/toolbar-button.tsx similarity index 100% rename from packages/react-email/src/components/toolbar/toolbar-button.tsx rename to packages/preview-server/src/components/toolbar/toolbar-button.tsx diff --git a/packages/react-email/src/components/toolbar/use-cached-state.ts b/packages/preview-server/src/components/toolbar/use-cached-state.ts similarity index 100% rename from packages/react-email/src/components/toolbar/use-cached-state.ts rename to packages/preview-server/src/components/toolbar/use-cached-state.ts diff --git a/packages/react-email/src/components/tooltip-content.tsx b/packages/preview-server/src/components/tooltip-content.tsx similarity index 100% rename from packages/react-email/src/components/tooltip-content.tsx rename to packages/preview-server/src/components/tooltip-content.tsx diff --git a/packages/react-email/src/components/tooltip.tsx b/packages/preview-server/src/components/tooltip.tsx similarity index 100% rename from packages/react-email/src/components/tooltip.tsx rename to packages/preview-server/src/components/tooltip.tsx diff --git a/packages/react-email/src/components/topbar.tsx b/packages/preview-server/src/components/topbar.tsx similarity index 100% rename from packages/react-email/src/components/topbar.tsx rename to packages/preview-server/src/components/topbar.tsx diff --git a/packages/react-email/src/components/topbar/active-view-toggle-group.tsx b/packages/preview-server/src/components/topbar/active-view-toggle-group.tsx similarity index 100% rename from packages/react-email/src/components/topbar/active-view-toggle-group.tsx rename to packages/preview-server/src/components/topbar/active-view-toggle-group.tsx diff --git a/packages/react-email/src/components/topbar/view-size-controls.tsx b/packages/preview-server/src/components/topbar/view-size-controls.tsx similarity index 100% rename from packages/react-email/src/components/topbar/view-size-controls.tsx rename to packages/preview-server/src/components/topbar/view-size-controls.tsx diff --git a/packages/react-email/src/contexts/emails.tsx b/packages/preview-server/src/contexts/emails.tsx similarity index 100% rename from packages/react-email/src/contexts/emails.tsx rename to packages/preview-server/src/contexts/emails.tsx diff --git a/packages/react-email/src/contexts/fragment-identifier.tsx b/packages/preview-server/src/contexts/fragment-identifier.tsx similarity index 100% rename from packages/react-email/src/contexts/fragment-identifier.tsx rename to packages/preview-server/src/contexts/fragment-identifier.tsx diff --git a/packages/react-email/src/contexts/preview.tsx b/packages/preview-server/src/contexts/preview.tsx similarity index 100% rename from packages/react-email/src/contexts/preview.tsx rename to packages/preview-server/src/contexts/preview.tsx diff --git a/packages/react-email/src/hooks/use-clamped-state.ts b/packages/preview-server/src/hooks/use-clamped-state.ts similarity index 100% rename from packages/react-email/src/hooks/use-clamped-state.ts rename to packages/preview-server/src/hooks/use-clamped-state.ts diff --git a/packages/react-email/src/hooks/use-email-rendering-result.ts b/packages/preview-server/src/hooks/use-email-rendering-result.ts similarity index 100% rename from packages/react-email/src/hooks/use-email-rendering-result.ts rename to packages/preview-server/src/hooks/use-email-rendering-result.ts diff --git a/packages/react-email/src/hooks/use-fragment-identifier.ts b/packages/preview-server/src/hooks/use-fragment-identifier.ts similarity index 100% rename from packages/react-email/src/hooks/use-fragment-identifier.ts rename to packages/preview-server/src/hooks/use-fragment-identifier.ts diff --git a/packages/react-email/src/hooks/use-hot-reload.ts b/packages/preview-server/src/hooks/use-hot-reload.ts similarity index 100% rename from packages/react-email/src/hooks/use-hot-reload.ts rename to packages/preview-server/src/hooks/use-hot-reload.ts diff --git a/packages/react-email/src/hooks/use-icon-animation.ts b/packages/preview-server/src/hooks/use-icon-animation.ts similarity index 100% rename from packages/react-email/src/hooks/use-icon-animation.ts rename to packages/preview-server/src/hooks/use-icon-animation.ts diff --git a/packages/react-email/src/hooks/use-rendering-metadata.ts b/packages/preview-server/src/hooks/use-rendering-metadata.ts similarity index 100% rename from packages/react-email/src/hooks/use-rendering-metadata.ts rename to packages/preview-server/src/hooks/use-rendering-metadata.ts diff --git a/packages/react-email/src/utils/__snapshots__/get-email-component.spec.ts.snap b/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap similarity index 100% rename from packages/react-email/src/utils/__snapshots__/get-email-component.spec.ts.snap rename to packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap diff --git a/packages/react-email/src/utils/caniemail/all-css-properties.ts b/packages/preview-server/src/utils/caniemail/all-css-properties.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/all-css-properties.ts rename to packages/preview-server/src/utils/caniemail/all-css-properties.ts diff --git a/packages/react-email/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap b/packages/preview-server/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap similarity index 100% rename from packages/react-email/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap rename to packages/preview-server/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap diff --git a/packages/react-email/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap b/packages/preview-server/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap similarity index 100% rename from packages/react-email/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap rename to packages/preview-server/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap diff --git a/packages/react-email/src/utils/caniemail/ast/get-object-variables.spec.ts b/packages/preview-server/src/utils/caniemail/ast/get-object-variables.spec.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/ast/get-object-variables.spec.ts rename to packages/preview-server/src/utils/caniemail/ast/get-object-variables.spec.ts diff --git a/packages/react-email/src/utils/caniemail/ast/get-object-variables.ts b/packages/preview-server/src/utils/caniemail/ast/get-object-variables.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/ast/get-object-variables.ts rename to packages/preview-server/src/utils/caniemail/ast/get-object-variables.ts diff --git a/packages/react-email/src/utils/caniemail/ast/get-used-style-properties.spec.ts b/packages/preview-server/src/utils/caniemail/ast/get-used-style-properties.spec.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/ast/get-used-style-properties.spec.ts rename to packages/preview-server/src/utils/caniemail/ast/get-used-style-properties.spec.ts diff --git a/packages/react-email/src/utils/caniemail/ast/get-used-style-properties.ts b/packages/preview-server/src/utils/caniemail/ast/get-used-style-properties.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/ast/get-used-style-properties.ts rename to packages/preview-server/src/utils/caniemail/ast/get-used-style-properties.ts diff --git a/packages/react-email/src/utils/caniemail/get-compatibility-stats-for-entry.ts b/packages/preview-server/src/utils/caniemail/get-compatibility-stats-for-entry.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-compatibility-stats-for-entry.ts rename to packages/preview-server/src/utils/caniemail/get-compatibility-stats-for-entry.ts diff --git a/packages/react-email/src/utils/caniemail/get-css-functions.ts b/packages/preview-server/src/utils/caniemail/get-css-functions.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-css-functions.ts rename to packages/preview-server/src/utils/caniemail/get-css-functions.ts diff --git a/packages/react-email/src/utils/caniemail/get-css-property-names.ts b/packages/preview-server/src/utils/caniemail/get-css-property-names.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-css-property-names.ts rename to packages/preview-server/src/utils/caniemail/get-css-property-names.ts diff --git a/packages/react-email/src/utils/caniemail/get-css-property-with-value.ts b/packages/preview-server/src/utils/caniemail/get-css-property-with-value.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-css-property-with-value.ts rename to packages/preview-server/src/utils/caniemail/get-css-property-with-value.ts diff --git a/packages/react-email/src/utils/caniemail/get-css-unit.ts b/packages/preview-server/src/utils/caniemail/get-css-unit.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-css-unit.ts rename to packages/preview-server/src/utils/caniemail/get-css-unit.ts diff --git a/packages/react-email/src/utils/caniemail/get-element-attributes.ts b/packages/preview-server/src/utils/caniemail/get-element-attributes.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-element-attributes.ts rename to packages/preview-server/src/utils/caniemail/get-element-attributes.ts diff --git a/packages/react-email/src/utils/caniemail/get-element-names.ts b/packages/preview-server/src/utils/caniemail/get-element-names.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/get-element-names.ts rename to packages/preview-server/src/utils/caniemail/get-element-names.ts diff --git a/packages/react-email/src/utils/caniemail/tailwind/generate-tailwind-rules.ts b/packages/preview-server/src/utils/caniemail/tailwind/generate-tailwind-rules.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/tailwind/generate-tailwind-rules.ts rename to packages/preview-server/src/utils/caniemail/tailwind/generate-tailwind-rules.ts diff --git a/packages/react-email/src/utils/caniemail/tailwind/get-tailwind-config.ts b/packages/preview-server/src/utils/caniemail/tailwind/get-tailwind-config.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/tailwind/get-tailwind-config.ts rename to packages/preview-server/src/utils/caniemail/tailwind/get-tailwind-config.ts diff --git a/packages/react-email/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts b/packages/preview-server/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts rename to packages/preview-server/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts diff --git a/packages/react-email/src/utils/caniemail/tailwind/get-tailwind-metadata.ts b/packages/preview-server/src/utils/caniemail/tailwind/get-tailwind-metadata.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/tailwind/get-tailwind-metadata.ts rename to packages/preview-server/src/utils/caniemail/tailwind/get-tailwind-metadata.ts diff --git a/packages/react-email/src/utils/caniemail/tailwind/setup-tailwind-context.ts b/packages/preview-server/src/utils/caniemail/tailwind/setup-tailwind-context.ts similarity index 100% rename from packages/react-email/src/utils/caniemail/tailwind/setup-tailwind-context.ts rename to packages/preview-server/src/utils/caniemail/tailwind/setup-tailwind-context.ts diff --git a/packages/react-email/src/utils/cn.ts b/packages/preview-server/src/utils/cn.ts similarity index 100% rename from packages/react-email/src/utils/cn.ts rename to packages/preview-server/src/utils/cn.ts diff --git a/packages/react-email/src/utils/constants.ts b/packages/preview-server/src/utils/constants.ts similarity index 100% rename from packages/react-email/src/utils/constants.ts rename to packages/preview-server/src/utils/constants.ts diff --git a/packages/react-email/src/utils/contains-email-template.spec.ts b/packages/preview-server/src/utils/contains-email-template.spec.ts similarity index 100% rename from packages/react-email/src/utils/contains-email-template.spec.ts rename to packages/preview-server/src/utils/contains-email-template.spec.ts diff --git a/packages/react-email/src/utils/contains-email-template.ts b/packages/preview-server/src/utils/contains-email-template.ts similarity index 100% rename from packages/react-email/src/utils/contains-email-template.ts rename to packages/preview-server/src/utils/contains-email-template.ts diff --git a/packages/react-email/src/utils/copy-text-to-clipboard.ts b/packages/preview-server/src/utils/copy-text-to-clipboard.ts similarity index 100% rename from packages/react-email/src/utils/copy-text-to-clipboard.ts rename to packages/preview-server/src/utils/copy-text-to-clipboard.ts diff --git a/packages/preview-server/src/utils/esbuild/escape-string-for-regex.ts b/packages/preview-server/src/utils/esbuild/escape-string-for-regex.ts new file mode 100644 index 0000000000..71d343cd9a --- /dev/null +++ b/packages/preview-server/src/utils/esbuild/escape-string-for-regex.ts @@ -0,0 +1,3 @@ +export function escapeStringForRegex(string: string) { + return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d'); +} diff --git a/packages/preview-server/src/utils/esbuild/renderring-utilities-exporter.ts b/packages/preview-server/src/utils/esbuild/renderring-utilities-exporter.ts new file mode 100644 index 0000000000..4856b15577 --- /dev/null +++ b/packages/preview-server/src/utils/esbuild/renderring-utilities-exporter.ts @@ -0,0 +1,63 @@ +import { promises as fs } from 'node:fs'; +import path from 'node:path'; +import type { Loader, PluginBuild, ResolveOptions } from 'esbuild'; +import { escapeStringForRegex } from './escape-string-for-regex'; + +/** + * Made to export the `render` function out of the user's email template + * so that issues like https://github.com/resend/react-email/issues/649 don't + * happen. + * + * This also exports the `createElement` from the user's React version as well + * to avoid mismatches. + * + * This avoids multiple versions of React being involved, i.e., the version + * in the CLI vs. the version the user has on their emails. + */ +export const renderingUtilitiesExporter = (emailTemplates: string[]) => ({ + name: 'rendering-utilities-exporter', + setup: (b: PluginBuild) => { + b.onLoad( + { + filter: new RegExp( + emailTemplates + .map((emailPath) => escapeStringForRegex(emailPath)) + .join('|'), + ), + }, + async ({ path: pathToFile }) => { + return { + contents: `${await fs.readFile(pathToFile, 'utf8')}; + export { render } from 'react-email-module-that-will-export-render' + export { createElement as reactEmailCreateReactElement } from 'react'; + `, + loader: path.extname(pathToFile).slice(1) as Loader, + }; + }, + ); + + b.onResolve( + { filter: /^react-email-module-that-will-export-render$/ }, + async (args) => { + const options: ResolveOptions = { + kind: 'import-statement', + importer: args.importer, + resolveDir: args.resolveDir, + namespace: args.namespace, + }; + let result = await b.resolve('@react-email/render', options); + if (result.errors.length === 0) { + return result; + } + + // If @react-email/render does not exist, resolve to @react-email/components + result = await b.resolve('@react-email/components', options); + if (result.errors.length > 0 && result.errors[0]) { + result.errors[0].text = + "Failed trying to import `render` from either `@react-email/render` or `@react-email/components` to be able to render your email template.\n Maybe you don't have either of them installed?"; + } + return result; + }, + ); + }, +}); diff --git a/packages/react-email/src/utils/get-email-component.spec.ts b/packages/preview-server/src/utils/get-email-component.spec.ts similarity index 100% rename from packages/react-email/src/utils/get-email-component.spec.ts rename to packages/preview-server/src/utils/get-email-component.spec.ts diff --git a/packages/react-email/src/utils/get-email-component.ts b/packages/preview-server/src/utils/get-email-component.ts similarity index 100% rename from packages/react-email/src/utils/get-email-component.ts rename to packages/preview-server/src/utils/get-email-component.ts diff --git a/packages/preview-server/src/utils/get-emails-directory-metadata.spec.ts b/packages/preview-server/src/utils/get-emails-directory-metadata.spec.ts new file mode 100644 index 0000000000..1f7aaae36b --- /dev/null +++ b/packages/preview-server/src/utils/get-emails-directory-metadata.spec.ts @@ -0,0 +1,82 @@ +import path from 'node:path'; +import { getEmailsDirectoryMetadata } from './get-emails-directory-metadata'; + +test('getEmailsDirectoryMetadata on demo emails', async () => { + const emailsDirectoryPath = path.resolve( + __dirname, + '../../../../apps/demo/emails', + ); + expect(await getEmailsDirectoryMetadata(emailsDirectoryPath)).toEqual({ + absolutePath: emailsDirectoryPath, + directoryName: 'emails', + relativePath: '', + emailFilenames: [], + subDirectories: [ + { + absolutePath: `${emailsDirectoryPath}/magic-links`, + directoryName: 'magic-links', + relativePath: 'magic-links', + emailFilenames: [ + 'aws-verify-email', + 'linear-login-code', + 'notion-magic-link', + 'plaid-verify-identity', + 'raycast-magic-link', + 'slack-confirm', + ], + subDirectories: [], + }, + { + absolutePath: `${emailsDirectoryPath}/newsletters`, + directoryName: 'newsletters', + relativePath: 'newsletters', + emailFilenames: [ + 'codepen-challengers', + 'google-play-policy-update', + 'stack-overflow-tips', + ], + subDirectories: [], + }, + { + absolutePath: `${emailsDirectoryPath}/notifications`, + directoryName: 'notifications', + relativePath: 'notifications', + emailFilenames: [ + 'github-access-token', + 'papermark-year-in-review', + 'vercel-invite-user', + 'yelp-recent-login', + ], + subDirectories: [], + }, + { + absolutePath: `${emailsDirectoryPath}/receipts`, + directoryName: 'receipts', + relativePath: 'receipts', + emailFilenames: ['apple-receipt', 'nike-receipt'], + subDirectories: [], + }, + { + absolutePath: `${emailsDirectoryPath}/reset-password`, + directoryName: 'reset-password', + relativePath: 'reset-password', + emailFilenames: ['dropbox-reset-password', 'twitch-reset-password'], + subDirectories: [], + }, + { + absolutePath: `${emailsDirectoryPath}/reviews`, + directoryName: 'reviews', + relativePath: 'reviews', + emailFilenames: ['airbnb-review', 'amazon-review'], + subDirectories: [], + }, + { + absolutePath: `${emailsDirectoryPath}/welcome`, + directoryName: 'welcome', + relativePath: 'welcome', + emailFilenames: ['koala-welcome', 'netlify-welcome', 'stripe-welcome'], + subDirectories: [], + }, + ], + }); +}); diff --git a/packages/preview-server/src/utils/get-emails-directory-metadata.ts b/packages/preview-server/src/utils/get-emails-directory-metadata.ts new file mode 100644 index 0000000000..939f71f895 --- /dev/null +++ b/packages/preview-server/src/utils/get-emails-directory-metadata.ts @@ -0,0 +1,141 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import fs from 'node:fs'; +import path from 'node:path'; + +const isFileAnEmail = async (fullPath: string): Promise => { + let fileHandle: fs.promises.FileHandle; + try { + fileHandle = await fs.promises.open(fullPath, 'r'); + } catch (exception) { + console.warn(exception); + return false; + } + const stat = await fileHandle.stat(); + + if (stat.isDirectory()) { + await fileHandle.close(); + return false; + } + + const { ext } = path.parse(fullPath); + + if (!['.js', '.tsx', '.jsx'].includes(ext)) { + await fileHandle.close(); + return false; + } + + // check with a heuristic to see if the file has at least + // a default export (ES6) or module.exports (CommonJS) or named exports (MDX) + const fileContents = await fileHandle.readFile('utf8'); + + await fileHandle.close(); + + // Check for ES6 export default syntax + const hasES6DefaultExport = /\bexport\s+default\b/gm.test(fileContents); + + // Check for CommonJS module.exports syntax + const hasCommonJSExport = /\bmodule\.exports\s*=/gm.test(fileContents); + + // Check for named exports (used in MDX files) and ensure at least one is marked as default + const hasNamedExport = /\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test( + fileContents, + ); + + return hasES6DefaultExport || hasCommonJSExport || hasNamedExport; +}; + +export interface EmailsDirectory { + absolutePath: string; + relativePath: string; + directoryName: string; + emailFilenames: string[]; + subDirectories: EmailsDirectory[]; +} + +const mergeDirectoriesWithSubDirectories = ( + emailsDirectoryMetadata: EmailsDirectory, +): EmailsDirectory => { + let currentResultingMergedDirectory: EmailsDirectory = + emailsDirectoryMetadata; + + while ( + currentResultingMergedDirectory.emailFilenames.length === 0 && + currentResultingMergedDirectory.subDirectories.length === 1 + ) { + const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0]!; + currentResultingMergedDirectory = { + ...onlySubDirectory, + directoryName: path.join( + currentResultingMergedDirectory.directoryName, + onlySubDirectory.directoryName, + ), + }; + } + + return currentResultingMergedDirectory; +}; + +export const getEmailsDirectoryMetadata = async ( + absolutePathToEmailsDirectory: string, + keepFileExtensions = false, + isSubDirectory = false, + + baseDirectoryPath = absolutePathToEmailsDirectory, +): Promise => { + if (!fs.existsSync(absolutePathToEmailsDirectory)) return; + + const dirents = await fs.promises.readdir(absolutePathToEmailsDirectory, { + withFileTypes: true, + }); + + const isEmailPredicates = await Promise.all( + dirents.map((dirent) => + isFileAnEmail(path.join(absolutePathToEmailsDirectory, dirent.name)), + ), + ); + const emailFilenames = dirents + .filter((_, i) => isEmailPredicates[i]) + .map((dirent) => + keepFileExtensions + ? dirent.name + : dirent.name.replace(path.extname(dirent.name), ''), + ); + + const subDirectories = await Promise.all( + dirents + .filter( + (dirent) => + dirent.isDirectory() && + !dirent.name.startsWith('_') && + dirent.name !== 'static', + ) + .map((dirent) => { + const direntAbsolutePath = path.join( + absolutePathToEmailsDirectory, + dirent.name, + ); + + return getEmailsDirectoryMetadata( + direntAbsolutePath, + keepFileExtensions, + true, + baseDirectoryPath, + ) as Promise; + }), + ); + + const emailsMetadata = { + absolutePath: absolutePathToEmailsDirectory, + relativePath: path.relative( + baseDirectoryPath, + absolutePathToEmailsDirectory, + ), + directoryName: absolutePathToEmailsDirectory.split(path.sep).pop()!, + emailFilenames, + subDirectories, + } satisfies EmailsDirectory; + + return isSubDirectory + ? mergeDirectoriesWithSubDirectories(emailsMetadata) + : emailsMetadata; +}; diff --git a/packages/react-email/src/utils/get-line-and-column-from-offset.spec.ts b/packages/preview-server/src/utils/get-line-and-column-from-offset.spec.ts similarity index 100% rename from packages/react-email/src/utils/get-line-and-column-from-offset.spec.ts rename to packages/preview-server/src/utils/get-line-and-column-from-offset.spec.ts diff --git a/packages/react-email/src/utils/get-line-and-column-from-offset.ts b/packages/preview-server/src/utils/get-line-and-column-from-offset.ts similarity index 100% rename from packages/react-email/src/utils/get-line-and-column-from-offset.ts rename to packages/preview-server/src/utils/get-line-and-column-from-offset.ts diff --git a/packages/react-email/src/utils/improve-error-with-sourcemap.ts b/packages/preview-server/src/utils/improve-error-with-sourcemap.ts similarity index 100% rename from packages/react-email/src/utils/improve-error-with-sourcemap.ts rename to packages/preview-server/src/utils/improve-error-with-sourcemap.ts diff --git a/packages/preview-server/src/utils/index.ts b/packages/preview-server/src/utils/index.ts new file mode 100644 index 0000000000..7a3cd2e7b6 --- /dev/null +++ b/packages/preview-server/src/utils/index.ts @@ -0,0 +1,6 @@ +export * from './cn'; +export * from './copy-text-to-clipboard'; +export * from './language-map'; +export * from './sanitize'; +export * from './types/as'; +export * from './unreachable'; diff --git a/packages/react-email/src/utils/js-email-detection.spec.ts b/packages/preview-server/src/utils/js-email-detection.spec.ts similarity index 100% rename from packages/react-email/src/utils/js-email-detection.spec.ts rename to packages/preview-server/src/utils/js-email-detection.spec.ts diff --git a/packages/react-email/src/utils/language-map.ts b/packages/preview-server/src/utils/language-map.ts similarity index 100% rename from packages/react-email/src/utils/language-map.ts rename to packages/preview-server/src/utils/language-map.ts diff --git a/packages/react-email/src/utils/linting.ts b/packages/preview-server/src/utils/linting.ts similarity index 100% rename from packages/react-email/src/utils/linting.ts rename to packages/preview-server/src/utils/linting.ts diff --git a/packages/react-email/src/utils/load-stream.ts b/packages/preview-server/src/utils/load-stream.ts similarity index 100% rename from packages/react-email/src/utils/load-stream.ts rename to packages/preview-server/src/utils/load-stream.ts diff --git a/packages/preview-server/src/utils/register-spinner-autostopping.ts b/packages/preview-server/src/utils/register-spinner-autostopping.ts new file mode 100644 index 0000000000..2a4a156b4d --- /dev/null +++ b/packages/preview-server/src/utils/register-spinner-autostopping.ts @@ -0,0 +1,28 @@ +import logSymbols from 'log-symbols'; +import type { Ora } from 'ora'; + +const spinners = new Set(); + +process.on('SIGINT', () => { + spinners.forEach((spinner) => { + if (spinner.isSpinning) { + spinner.stop(); + } + }); +}); + +process.on('exit', (code) => { + if (code !== 0) { + spinners.forEach((spinner) => { + if (spinner.isSpinning) { + spinner.stopAndPersist({ + symbol: logSymbols.error, + }); + } + }); + } +}); + +export const registerSpinnerAutostopping = (spinner: Ora) => { + spinners.add(spinner); +}; diff --git a/packages/react-email/src/utils/result.ts b/packages/preview-server/src/utils/result.ts similarity index 100% rename from packages/react-email/src/utils/result.ts rename to packages/preview-server/src/utils/result.ts diff --git a/packages/react-email/src/utils/run-bundled-code.ts b/packages/preview-server/src/utils/run-bundled-code.ts similarity index 100% rename from packages/react-email/src/utils/run-bundled-code.ts rename to packages/preview-server/src/utils/run-bundled-code.ts diff --git a/packages/react-email/src/utils/sanitize.ts b/packages/preview-server/src/utils/sanitize.ts similarity index 100% rename from packages/react-email/src/utils/sanitize.ts rename to packages/preview-server/src/utils/sanitize.ts diff --git a/packages/react-email/src/utils/static-node-modules-for-vm.ts b/packages/preview-server/src/utils/static-node-modules-for-vm.ts similarity index 100% rename from packages/react-email/src/utils/static-node-modules-for-vm.ts rename to packages/preview-server/src/utils/static-node-modules-for-vm.ts diff --git a/packages/react-email/src/utils/testing/js-email-export-default.js b/packages/preview-server/src/utils/testing/js-email-export-default.js similarity index 100% rename from packages/react-email/src/utils/testing/js-email-export-default.js rename to packages/preview-server/src/utils/testing/js-email-export-default.js diff --git a/packages/react-email/src/utils/testing/js-email-test.js b/packages/preview-server/src/utils/testing/js-email-test.js similarity index 100% rename from packages/react-email/src/utils/testing/js-email-test.js rename to packages/preview-server/src/utils/testing/js-email-test.js diff --git a/packages/react-email/src/utils/testing/mdx-email-test.js b/packages/preview-server/src/utils/testing/mdx-email-test.js similarity index 100% rename from packages/react-email/src/utils/testing/mdx-email-test.js rename to packages/preview-server/src/utils/testing/mdx-email-test.js diff --git a/packages/react-email/src/utils/testing/request-response-email.tsx b/packages/preview-server/src/utils/testing/request-response-email.tsx similarity index 100% rename from packages/react-email/src/utils/testing/request-response-email.tsx rename to packages/preview-server/src/utils/testing/request-response-email.tsx diff --git a/packages/react-email/src/utils/types/as.ts b/packages/preview-server/src/utils/types/as.ts similarity index 100% rename from packages/react-email/src/utils/types/as.ts rename to packages/preview-server/src/utils/types/as.ts diff --git a/packages/react-email/src/utils/types/email-template.ts b/packages/preview-server/src/utils/types/email-template.ts similarity index 100% rename from packages/react-email/src/utils/types/email-template.ts rename to packages/preview-server/src/utils/types/email-template.ts diff --git a/packages/react-email/src/utils/types/error-object.ts b/packages/preview-server/src/utils/types/error-object.ts similarity index 100% rename from packages/react-email/src/utils/types/error-object.ts rename to packages/preview-server/src/utils/types/error-object.ts diff --git a/packages/preview-server/src/utils/types/hot-reload-change.ts b/packages/preview-server/src/utils/types/hot-reload-change.ts new file mode 100644 index 0000000000..15b6be7d89 --- /dev/null +++ b/packages/preview-server/src/utils/types/hot-reload-change.ts @@ -0,0 +1,13 @@ +export interface HotReloadChange { + filename: string; + event: + | 'all' + | 'ready' + | 'add' + | 'change' + | 'addDir' + | 'unlink' + | 'unlinkDir' + | 'raw' + | 'error'; +} diff --git a/packages/react-email/src/utils/unreachable.ts b/packages/preview-server/src/utils/unreachable.ts similarity index 100% rename from packages/react-email/src/utils/unreachable.ts rename to packages/preview-server/src/utils/unreachable.ts diff --git a/packages/react-email/tailwind-internals.d.ts b/packages/preview-server/tailwind-internals.d.ts similarity index 100% rename from packages/react-email/tailwind-internals.d.ts rename to packages/preview-server/tailwind-internals.d.ts diff --git a/packages/react-email/tailwind.config.ts b/packages/preview-server/tailwind.config.ts similarity index 100% rename from packages/react-email/tailwind.config.ts rename to packages/preview-server/tailwind.config.ts diff --git a/packages/preview-server/tsconfig.json b/packages/preview-server/tsconfig.json new file mode 100644 index 0000000000..7a12ec6b6a --- /dev/null +++ b/packages/preview-server/tsconfig.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Next.js", + "compilerOptions": { + "composite": false, + "downlevelIteration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "node", + "noUnusedLocals": false, + "noUnusedParameters": false, + "preserveWatchOutput": true, + "skipLibCheck": true, + "strictNullChecks": true, + "plugins": [ + { + "name": "next" + } + ], + "allowJs": true, + "declaration": false, + "declarationMap": false, + "incremental": false, + "jsx": "preserve", + "lib": ["dom", "dom.iterable", "esnext", "ESNext.AsyncIterable"], + "noEmit": true, + "strict": false, + "target": "ESNext", + "module": "CommonJS", + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, + "types": ["vitest/globals"], + "outDir": "dist" + }, + "include": [ + "next-env.d.ts", + "tailwind-internals.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [".next", "dist", "node_modules"] +} diff --git a/packages/preview-server/vitest.config.ts b/packages/preview-server/vitest.config.ts new file mode 100644 index 0000000000..bf6076a810 --- /dev/null +++ b/packages/preview-server/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'happy-dom', + }, + esbuild: { + tsconfigRaw: { + compilerOptions: { + jsx: 'react-jsx', + }, + }, + }, +}); diff --git a/packages/react-email/package.json b/packages/react-email/package.json index e71af09715..280684e939 100644 --- a/packages/react-email/package.json +++ b/packages/react-email/package.json @@ -3,15 +3,13 @@ "version": "4.1.0-canary.7", "description": "A live preview of your emails right in your browser.", "bin": { - "email": "./dist/cli/index.mjs" + "email": "./dist/index.js" }, + "type": "module", "scripts": { - "build": "tsup-node && node ./scripts/build-preview-server.mjs", - "postbuild": "pnpm install --frozen-lockfile", - "caniemail:fetch": "node ./scripts/fill-caniemail-data.mjs", + "build": "tsup-node", "clean": "rm -rf dist", - "dev": "tsup-node --watch", - "dev:preview": "cd ../../apps/demo && tsx ../../packages/react-email/src/cli/index.ts dev", + "dev": "tsup-node --watch src", "test": "vitest run", "test:watch": "vitest" }, @@ -37,60 +35,27 @@ "debounce": "^2.0.0", "esbuild": "^0.25.0", "glob": "^11.0.0", + "jiti": "2.4.2", "log-symbols": "^7.0.0", "mime-types": "^3.0.0", - "next": "^15.3.1", "normalize-path": "^3.0.0", + "nypm": "0.6.0", "ora": "^8.0.0", + "prompts": "2.4.2", "socket.io": "^4.8.1", "tsconfig-paths": "4.2.0" }, "devDependencies": { - "@babel/core": "7.26.10", - "@lottiefiles/dotlottie-react": "0.13.3", - "@radix-ui/colors": "3.0.0", - "@radix-ui/react-collapsible": "1.1.7", - "@radix-ui/react-dropdown-menu": "2.1.10", - "@radix-ui/react-popover": "1.1.10", - "@radix-ui/react-slot": "1.2.0", - "@radix-ui/react-tabs": "1.1.7", - "@radix-ui/react-toggle-group": "1.1.6", - "@radix-ui/react-tooltip": "1.2.3", "@react-email/components": "workspace:*", - "@swc/core": "1.11.21", "@types/babel__core": "7.20.5", "@types/babel__traverse": "7.20.7", - "@types/fs-extra": "11.0.1", "@types/mime-types": "2.1.4", - "@types/node": "22.14.1", - "@types/normalize-path": "3.0.2", - "@types/react": "19.0.10", - "@types/react-dom": "19.0.4", - "@types/webpack": "5.28.5", - "autoprefixer": "10.4.21", - "clsx": "2.1.1", - "framer-motion": "12.7.5", - "jiti": "2.4.2", - "json5": "2.2.3", - "module-punycode": "npm:punycode@2.3.1", - "node-html-parser": "7.0.1", - "postcss": "8.5.3", - "pretty-bytes": "6.1.1", - "prism-react-renderer": "2.4.1", + "@types/prompts": "2.4.9", + "next": "^15.3.1", "react": "19.0.0", "react-dom": "19.0.0", - "sharp": "0.34.1", - "socket.io-client": "4.8.1", - "sonner": "2.0.3", - "source-map-js": "1.2.1", - "spamc": "0.0.5", - "stacktrace-parser": "0.1.11", - "tailwind-merge": "3.2.0", - "tailwindcss": "3.4.0", "tsup": "8.4.0", "tsx": "4.19.3", - "typescript": "5.8.3", - "use-debounce": "10.0.4", - "zod": "3.24.3" + "typescript": "5.8.3" } } diff --git a/packages/react-email/scripts/build-preview-server.mjs b/packages/react-email/scripts/build-preview-server.mjs deleted file mode 100644 index 8c54a66083..0000000000 --- a/packages/react-email/scripts/build-preview-server.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { spawn } from 'node:child_process'; -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const nextBuildProcess = spawn('pnpm', ['next', 'build'], { - detached: true, - shell: true, - stdio: 'inherit', - cwd: path.resolve(__dirname, '../'), -}); - -process.on('SIGINT', () => { - nextBuildProcess.kill('SIGINT'); -}); - -nextBuildProcess.on('exit', (code) => { - if (code !== 0) { - console.error(`next build failed with exit code ${code}`); - process.exit(code); - } - - const builtPreviewPath = path.resolve(__dirname, '../dist/preview'); - - if (fs.existsSync(builtPreviewPath)) { - fs.rmSync(builtPreviewPath, { recursive: true }); - } - fs.mkdirSync(builtPreviewPath, { recursive: true }); - fs.rmSync('.next/cache', { recursive: true }); - fs.renameSync('.next', path.join(builtPreviewPath, '/.next')); -}); diff --git a/packages/react-email/src/cli/commands/testing/export.spec.ts b/packages/react-email/src/cli/commands/testing/export.spec.ts deleted file mode 100644 index f75662ab54..0000000000 --- a/packages/react-email/src/cli/commands/testing/export.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { exportTemplates } from '../export'; - -test( - 'email export', - async () => { - const pathToEmailsDirectory = path.resolve( - __dirname, - '../../../../../../apps/demo/emails', - ); - const pathToDumpMarkup = path.resolve(__dirname, './out'); - await exportTemplates(pathToDumpMarkup, pathToEmailsDirectory, { - silent: true, - pretty: true, - }); - - expect(fs.existsSync(pathToDumpMarkup)).toBe(true); - expect( - await fs.promises.readFile( - path.resolve( - pathToDumpMarkup, - './notifications/vercel-invite-user.html', - ), - 'utf8', - ), - ).toMatchSnapshot(); - }, - { retry: 3 }, -); diff --git a/packages/react-email/src/cli/utils/index.ts b/packages/react-email/src/cli/utils/index.ts deleted file mode 100644 index 0aaa0b2ead..0000000000 --- a/packages/react-email/src/cli/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './preview'; -export * from './tree'; diff --git a/packages/react-email/src/cli/utils/preview/index.ts b/packages/react-email/src/cli/utils/preview/index.ts deleted file mode 100644 index 46a98521ab..0000000000 --- a/packages/react-email/src/cli/utils/preview/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './hot-reloading/setup-hot-reloading'; -export * from './start-dev-server'; diff --git a/packages/react-email/src/cli/utils/tree.spec.ts b/packages/react-email/src/cli/utils/tree.spec.ts deleted file mode 100644 index 29278f9393..0000000000 --- a/packages/react-email/src/cli/utils/tree.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { tree } from './tree'; - -test('tree(__dirname, 2)', async () => { - expect(await tree(__dirname, 2)).toMatchInlineSnapshot(`"utils -├── preview -│ ├── hot-reloading -│ ├── get-env-variables-for-preview-app.ts -│ ├── index.ts -│ ├── serve-static-file.ts -│ └── start-dev-server.ts -├── index.ts -├── tree.spec.ts -└── tree.ts"`); -}); diff --git a/packages/react-email/src/cli/commands/.npmignore b/packages/react-email/src/commands/.npmignore similarity index 100% rename from packages/react-email/src/cli/commands/.npmignore rename to packages/react-email/src/commands/.npmignore diff --git a/packages/react-email/src/cli/commands/build.ts b/packages/react-email/src/commands/build.ts similarity index 95% rename from packages/react-email/src/cli/commands/build.ts rename to packages/react-email/src/commands/build.ts index 04647740bf..a4394cc78e 100644 --- a/packages/react-email/src/cli/commands/build.ts +++ b/packages/react-email/src/commands/build.ts @@ -6,9 +6,9 @@ import ora from 'ora'; import { type EmailsDirectory, getEmailsDirectoryMetadata, -} from '../../utils/get-emails-directory-metadata'; -import { registerSpinnerAutostopping } from '../../utils/register-spinner-autostopping'; -import { cliPackageLocation } from '../utils'; +} from '../utils/get-emails-directory-metadata.js'; +import { getPreviewServerLocation } from '../utils/get-preview-server-location.js'; +import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopping.js'; interface Args { dir: string; @@ -38,6 +38,39 @@ const buildPreviewApp = (absoluteDirectory: string) => { }); }; +const npmInstall = async ( + builtPreviewAppPath: string, + packageManager: string, +) => { + return new Promise((resolve, reject) => { + const childProc = spawn( + packageManager, + [ + 'install', + packageManager === 'deno' ? '' : '--include=dev', + packageManager === 'deno' ? '--quiet' : '--silent', + ], + { + cwd: builtPreviewAppPath, + shell: true, + }, + ); + childProc.stdout.pipe(process.stdout); + childProc.stderr.pipe(process.stderr); + childProc.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject( + new Error( + `Unable to install the dependencies and it exited with code: ${code}`, + ), + ); + } + }); + }); +}; + const setNextEnvironmentVariablesForBuild = async ( emailsDirRelativePath: string, builtPreviewAppPath: string, @@ -45,7 +78,7 @@ const setNextEnvironmentVariablesForBuild = async ( const nextConfigContents = ` const path = require('path'); const emailsDirRelativePath = path.normalize('${emailsDirRelativePath}'); -const userProjectLocation = path.resolve(process.cwd(), '../'); +const userProjectLocation = '${process.cwd()}'; /** @type {import('next').NextConfig} */ module.exports = { env: { @@ -173,6 +206,7 @@ const updatePackageJson = async (builtPreviewAppPath: string) => { delete packageJson.scripts.postbuild; packageJson.name = 'preview-server'; + // We remove this one to avoid having resolve issues on our demo build process. // This is only used in the `export` command so it's irrelevant to have it here. // @@ -181,6 +215,7 @@ const updatePackageJson = async (builtPreviewAppPath: string) => { delete packageJson.devDependencies['@react-email/render']; delete packageJson.devDependencies['@react-email/components']; delete packageJson.scripts.prepare; + await fs.promises.writeFile( packageJsonPath, JSON.stringify(packageJson), @@ -188,44 +223,13 @@ const updatePackageJson = async (builtPreviewAppPath: string) => { ); }; -const npmInstall = async ( - builtPreviewAppPath: string, - packageManager: string, -) => { - return new Promise((resolve, reject) => { - const childProc = spawn( - packageManager, - [ - 'install', - packageManager === 'deno' ? '' : '--include=dev', - packageManager === 'deno' ? '--quiet' : '--silent', - ], - { - cwd: builtPreviewAppPath, - shell: true, - }, - ); - childProc.stdout.pipe(process.stdout); - childProc.stderr.pipe(process.stderr); - childProc.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject( - new Error( - `Unable to install the dependencies and it exited with code: ${code}`, - ), - ); - } - }); - }); -}; - export const build = async ({ dir: emailsDirRelativePath, packageManager, }: Args) => { try { + const previewServerLocation = await getPreviewServerLocation(); + const spinner = ora({ text: 'Starting build process...', prefixText: ' ', @@ -248,7 +252,7 @@ export const build = async ({ } spinner.text = 'Copying preview app from CLI to `.react-email`'; - await fs.promises.cp(cliPackageLocation, builtPreviewAppPath, { + await fs.promises.cp(previewServerLocation, builtPreviewAppPath, { recursive: true, filter: (source: string) => { // do not copy the CLI files diff --git a/packages/react-email/src/cli/commands/dev.ts b/packages/react-email/src/commands/dev.ts similarity index 90% rename from packages/react-email/src/cli/commands/dev.ts rename to packages/react-email/src/commands/dev.ts index 6f9ed6803d..f499041abd 100644 --- a/packages/react-email/src/cli/commands/dev.ts +++ b/packages/react-email/src/commands/dev.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import { setupHotreloading, startDevServer } from '../utils'; +import { setupHotreloading, startDevServer } from '../utils/index.js'; interface Args { dir: string; diff --git a/packages/react-email/src/cli/commands/export.ts b/packages/react-email/src/commands/export.ts similarity index 95% rename from packages/react-email/src/cli/commands/export.ts rename to packages/react-email/src/commands/export.ts index 442c4ee0e1..4bbdb5db47 100644 --- a/packages/react-email/src/cli/commands/export.ts +++ b/packages/react-email/src/commands/export.ts @@ -9,13 +9,13 @@ import logSymbols from 'log-symbols'; import normalize from 'normalize-path'; import ora, { type Ora } from 'ora'; import type React from 'react'; -import { renderingUtilitiesExporter } from '../../utils/esbuild/renderring-utilities-exporter'; +import { renderingUtilitiesExporter } from '../utils/esbuild/renderring-utilities-exporter.js'; import { type EmailsDirectory, getEmailsDirectoryMetadata, -} from '../../utils/get-emails-directory-metadata'; -import { registerSpinnerAutostopping } from '../../utils/register-spinner-autostopping'; -import { tree } from '../utils'; +} from '../utils/get-emails-directory-metadata.js'; +import { tree } from '../utils/index.js'; +import { registerSpinnerAutostopping } from '../utils/register-spinner-autostopping.js'; const getEmailTemplatesFromDirectory = (emailDirectory: EmailsDirectory) => { const templatePaths = [] as string[]; diff --git a/packages/react-email/src/cli/commands/start.ts b/packages/react-email/src/commands/start.ts similarity index 73% rename from packages/react-email/src/cli/commands/start.ts rename to packages/react-email/src/commands/start.ts index e5d1005d09..2a316c1f9b 100644 --- a/packages/react-email/src/cli/commands/start.ts +++ b/packages/react-email/src/commands/start.ts @@ -1,9 +1,12 @@ import { spawn } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; +import { getPreviewServerLocation } from '../utils/get-preview-server-location.js'; export const start = async () => { try { + const previewServerLocation = await getPreviewServerLocation(); + const usersProjectLocation = process.cwd(); const builtPreviewPath = path.resolve( usersProjectLocation, @@ -16,8 +19,8 @@ export const start = async () => { process.exit(1); } - const nextStart = spawn('npm', ['start'], { - cwd: builtPreviewPath, + const nextStart = spawn('npx', ['next', 'start', builtPreviewPath], { + cwd: previewServerLocation, stdio: 'inherit', }); diff --git a/packages/react-email/src/cli/commands/testing/.gitignore b/packages/react-email/src/commands/testing/.gitignore similarity index 100% rename from packages/react-email/src/cli/commands/testing/.gitignore rename to packages/react-email/src/commands/testing/.gitignore diff --git a/packages/react-email/src/cli/commands/testing/__snapshots__/export.spec.ts.snap b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap similarity index 100% rename from packages/react-email/src/cli/commands/testing/__snapshots__/export.spec.ts.snap rename to packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap diff --git a/packages/react-email/src/commands/testing/export.spec.ts b/packages/react-email/src/commands/testing/export.spec.ts new file mode 100644 index 0000000000..98a2731696 --- /dev/null +++ b/packages/react-email/src/commands/testing/export.spec.ts @@ -0,0 +1,23 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { exportTemplates } from '../export.js'; + +test('email export', { retry: 3 }, async () => { + const pathToEmailsDirectory = path.resolve( + __dirname, + '../../../../../apps/demo/emails', + ); + const pathToDumpMarkup = path.resolve(__dirname, './out'); + await exportTemplates(pathToDumpMarkup, pathToEmailsDirectory, { + silent: true, + pretty: true, + }); + + expect(fs.existsSync(pathToDumpMarkup)).toBe(true); + expect( + await fs.promises.readFile( + path.resolve(pathToDumpMarkup, './notifications/vercel-invite-user.html'), + 'utf8', + ), + ).toMatchSnapshot(); +}); diff --git a/packages/react-email/src/cli/index.ts b/packages/react-email/src/index.ts similarity index 86% rename from packages/react-email/src/cli/index.ts rename to packages/react-email/src/index.ts index 3cb25317c7..43f4fba483 100644 --- a/packages/react-email/src/cli/index.ts +++ b/packages/react-email/src/index.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node import { program } from 'commander'; -import packageJson from '../../package.json'; -import { build } from './commands/build'; -import { dev } from './commands/dev'; -import { exportTemplates } from './commands/export'; -import { start } from './commands/start'; +import { build } from './commands/build.js'; +import { dev } from './commands/dev.js'; +import { exportTemplates } from './commands/export.js'; +import { start } from './commands/start.js'; +import { packageJson } from './utils/packageJson.js'; const PACKAGE_NAME = 'react-email'; diff --git a/packages/react-email/src/utils/__snapshots__/tree.spec.ts.snap b/packages/react-email/src/utils/__snapshots__/tree.spec.ts.snap new file mode 100644 index 0000000000..2b35dc16bc --- /dev/null +++ b/packages/react-email/src/utils/__snapshots__/tree.spec.ts.snap @@ -0,0 +1,27 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`tree(__dirname, 2) 1`] = ` +"utils +├── __snapshots__ +│ └── tree.spec.ts.snap +├── esbuild +│ ├── escape-string-for-regex.ts +│ └── renderring-utilities-exporter.ts +├── preview +│ ├── hot-reloading +│ ├── get-env-variables-for-preview-app.ts +│ ├── index.ts +│ ├── serve-static-file.ts +│ └── start-dev-server.ts +├── types +│ ├── hot-reload-change.ts +│ └── hot-reload-event.ts +├── get-emails-directory-metadata.spec.ts +├── get-emails-directory-metadata.ts +├── get-preview-server-location.ts +├── index.ts +├── packageJson.ts +├── register-spinner-autostopping.ts +├── tree.spec.ts +└── tree.ts" +`; diff --git a/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts b/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts index 4856b15577..7f8fe62cc2 100644 --- a/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts +++ b/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts @@ -1,7 +1,7 @@ import { promises as fs } from 'node:fs'; import path from 'node:path'; import type { Loader, PluginBuild, ResolveOptions } from 'esbuild'; -import { escapeStringForRegex } from './escape-string-for-regex'; +import { escapeStringForRegex } from './escape-string-for-regex.js'; /** * Made to export the `render` function out of the user's email template diff --git a/packages/react-email/src/utils/get-emails-directory-metadata.spec.ts b/packages/react-email/src/utils/get-emails-directory-metadata.spec.ts index 1f7aaae36b..b91577a1b7 100644 --- a/packages/react-email/src/utils/get-emails-directory-metadata.spec.ts +++ b/packages/react-email/src/utils/get-emails-directory-metadata.spec.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { getEmailsDirectoryMetadata } from './get-emails-directory-metadata'; +import { getEmailsDirectoryMetadata } from './get-emails-directory-metadata.js'; test('getEmailsDirectoryMetadata on demo emails', async () => { const emailsDirectoryPath = path.resolve( diff --git a/packages/react-email/src/utils/get-preview-server-location.ts b/packages/react-email/src/utils/get-preview-server-location.ts new file mode 100644 index 0000000000..a438059beb --- /dev/null +++ b/packages/react-email/src/utils/get-preview-server-location.ts @@ -0,0 +1,32 @@ +import path from 'node:path'; +import url from 'node:url'; +import { createJiti } from 'jiti'; +import { addDevDependency } from 'nypm'; +import prompts from 'prompts'; + +export const getPreviewServerLocation = async () => { + const usersProject = createJiti(process.cwd()); + let previewServerLocation!: string; + try { + previewServerLocation = path.dirname( + url.parse(usersProject.esmResolve('@react-email/preview-server'), true) + .path!, + ); + } catch (exception) { + const response = await prompts({ + type: 'confirm', + name: 'installPreviewServer', + message: + 'To run the preview server, the pacakge "@react-email/preview-server" must be installed. Would you like to install it?', + initial: true, + }); + if (response.installPreviewServer) { + console.log('Installing "@react-email/preview-server"'); + await addDevDependency('@react-email/preview-server'); + process.exit(0); + } else { + process.exit(0); + } + } + return previewServerLocation; +}; diff --git a/packages/react-email/src/utils/index.ts b/packages/react-email/src/utils/index.ts index 7a3cd2e7b6..a52444541b 100644 --- a/packages/react-email/src/utils/index.ts +++ b/packages/react-email/src/utils/index.ts @@ -1,6 +1,2 @@ -export * from './cn'; -export * from './copy-text-to-clipboard'; -export * from './language-map'; -export * from './sanitize'; -export * from './types/as'; -export * from './unreachable'; +export * from './preview/index.js'; +export * from './tree.js'; diff --git a/packages/react-email/src/utils/packageJson.ts b/packages/react-email/src/utils/packageJson.ts new file mode 100644 index 0000000000..5c0b2d2a63 --- /dev/null +++ b/packages/react-email/src/utils/packageJson.ts @@ -0,0 +1,4 @@ +// @ts-expect-error Typescript doesn't want to allow this, but it's fine since we're using tsup +import packageJson from '../../package.json'; + +export { packageJson }; diff --git a/packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts b/packages/react-email/src/utils/preview/get-env-variables-for-preview-app.ts similarity index 89% rename from packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts rename to packages/react-email/src/utils/preview/get-env-variables-for-preview-app.ts index a293ca583f..ffb1634a62 100644 --- a/packages/react-email/src/cli/utils/preview/get-env-variables-for-preview-app.ts +++ b/packages/react-email/src/utils/preview/get-env-variables-for-preview-app.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { isDev } from './start-dev-server'; +import { isDev } from './start-dev-server.js'; export const getEnvVariablesForPreviewApp = ( relativePathToEmailsDirectory: string, diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/create-dependency-graph.spec.ts b/packages/react-email/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts similarity index 80% rename from packages/react-email/src/cli/utils/preview/hot-reloading/create-dependency-graph.spec.ts rename to packages/react-email/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts index 3dc9f1738c..ff1dbf8d3f 100644 --- a/packages/react-email/src/cli/utils/preview/hot-reloading/create-dependency-graph.spec.ts +++ b/packages/react-email/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import { createDependencyGraph, type DependencyGraph, -} from './create-dependency-graph'; +} from './create-dependency-graph.js'; const pathToFileForTestingDependencyGraph = path.join( __dirname, @@ -44,11 +44,11 @@ test('createDependencyGraph()', async () => { }; const initialDependencyGraph = convertPathsToAbsolute({ - '../../../../../package.json': { + '../../../../package.json': { dependencyPaths: [], - dependentPaths: ['../start-dev-server.ts'], + dependentPaths: ['../../packageJson.ts'], moduleDependencies: [], - path: '../../../../../package.json', + path: '../../../../package.json', }, 'create-dependency-graph.ts': { path: 'create-dependency-graph.ts', @@ -61,7 +61,19 @@ test('createDependencyGraph()', async () => { 'create-dependency-graph.spec.ts', 'setup-hot-reloading.ts', ], - moduleDependencies: ['node:fs', 'node:path', 'chokidar/handler'], + moduleDependencies: ['node:fs', 'node:path', 'chokidar/handler.js'], + }, + '../../get-preview-server-location.ts': { + dependencyPaths: [], + dependentPaths: ['../../preview/start-dev-server.ts'], + moduleDependencies: ['node:path', 'node:url', 'jiti', 'nypm', 'prompts'], + path: '../../get-preview-server-location.ts', + }, + '../../packageJson.ts': { + dependencyPaths: ['../../../../package.json'], + dependentPaths: ['../../preview/start-dev-server.ts'], + moduleDependencies: [], + path: '../../packageJson.ts', }, 'create-dependency-graph.spec.ts': { path: 'create-dependency-graph.spec.ts', @@ -69,11 +81,11 @@ test('createDependencyGraph()', async () => { dependentPaths: [], moduleDependencies: ['node:fs', 'node:path'], }, - '../../../utils/preview/get-env-variables-for-preview-app.ts': { - dependencyPaths: ['../../../utils/preview/start-dev-server.ts'], - dependentPaths: ['../../../utils/preview/start-dev-server.ts'], + '../get-env-variables-for-preview-app.ts': { + dependencyPaths: ['../../preview/start-dev-server.ts'], + dependentPaths: ['../../preview/start-dev-server.ts'], moduleDependencies: ['node:path'], - path: '../../../utils/preview/get-env-variables-for-preview-app.ts', + path: '../../preview/get-env-variables-for-preview-app.ts', }, './test/some-file.ts': { dependencyPaths: [], @@ -114,7 +126,7 @@ test('createDependencyGraph()', async () => { 'setup-hot-reloading.ts': { path: 'setup-hot-reloading.ts', dependencyPaths: [ - '../../../../utils/types/hot-reload-change.ts', + '../../types/hot-reload-change.ts', 'create-dependency-graph.ts', ], dependentPaths: [], @@ -128,14 +140,15 @@ test('createDependencyGraph()', async () => { }, '../start-dev-server.ts': { dependencyPaths: [ - '../../../../../package.json', - '../../../../utils/register-spinner-autostopping.ts', - '../../../utils/preview/get-env-variables-for-preview-app.ts', - '../../../utils/preview/serve-static-file.ts', + '../../register-spinner-autostopping.ts', + '../../get-preview-server-location.ts', + '../../packageJson.ts', + '../../preview/get-env-variables-for-preview-app.ts', + '../../preview/serve-static-file.ts', ], path: '../start-dev-server.ts', dependentPaths: [ - '../../../utils/preview/get-env-variables-for-preview-app.ts', + '../../preview/get-env-variables-for-preview-app.ts', 'create-dependency-graph.ts', ], moduleDependencies: [ @@ -143,14 +156,14 @@ test('createDependencyGraph()', async () => { 'node:path', 'node:url', 'chalk', + 'jiti', 'log-symbols', - 'next', 'ora', ], }, - '../../../utils/preview/serve-static-file.ts': { + '../../preview/serve-static-file.ts': { dependencyPaths: [], - dependentPaths: ['../../../utils/preview/start-dev-server.ts'], + dependentPaths: ['../../preview/start-dev-server.ts'], moduleDependencies: [ 'node:fs', 'node:http', @@ -158,23 +171,23 @@ test('createDependencyGraph()', async () => { 'node:url', 'mime-types', ], - path: '../../../utils/preview/serve-static-file.ts', + path: '../../preview/serve-static-file.ts', }, - '../../../../utils/register-spinner-autostopping.ts': { + '../../register-spinner-autostopping.ts': { dependencyPaths: [], - dependentPaths: ['../../../utils/preview/start-dev-server.ts'], + dependentPaths: ['../../preview/start-dev-server.ts'], moduleDependencies: ['log-symbols', 'ora'], - path: '../../../../utils/register-spinner-autostopping.ts', + path: '../../register-spinner-autostopping.ts', }, - '../../../../utils/types/hot-reload-event.ts': { + '../../types/hot-reload-event.ts': { dependencyPaths: [], - dependentPaths: ['../../../../utils/types/hot-reload-change.ts'], - moduleDependencies: ['chokidar/handler'], - path: '../../../../utils/types/hot-reload-event.ts', + dependentPaths: ['../../types/hot-reload-change.ts'], + moduleDependencies: ['chokidar/handler.js'], + path: '../../types/hot-reload-event.ts', }, - '../../../../utils/types/hot-reload-change.ts': { - path: '../../../../utils/types/hot-reload-change.ts', - dependencyPaths: ['../../../../utils/types/hot-reload-event.ts'], + '../../types/hot-reload-change.ts': { + path: '../../types/hot-reload-change.ts', + dependencyPaths: ['../../types/hot-reload-event.ts'], dependentPaths: ['setup-hot-reloading.ts'], moduleDependencies: [], }, diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/create-dependency-graph.ts b/packages/react-email/src/utils/preview/hot-reloading/create-dependency-graph.ts similarity index 97% rename from packages/react-email/src/cli/utils/preview/hot-reloading/create-dependency-graph.ts rename to packages/react-email/src/utils/preview/hot-reloading/create-dependency-graph.ts index 0462779dbb..1c50fd908b 100644 --- a/packages/react-email/src/cli/utils/preview/hot-reloading/create-dependency-graph.ts +++ b/packages/react-email/src/utils/preview/hot-reloading/create-dependency-graph.ts @@ -1,9 +1,9 @@ import { existsSync, promises as fs, statSync } from 'node:fs'; import path from 'node:path'; -import type { EventName } from 'chokidar/handler'; -import { isDev } from '../start-dev-server'; -import { getImportedModules } from './get-imported-modules'; -import { resolvePathAliases } from './resolve-path-aliases'; +import type { EventName } from 'chokidar/handler.js'; +import { isDev } from '../start-dev-server.js'; +import { getImportedModules } from './get-imported-modules.js'; +import { resolvePathAliases } from './resolve-path-aliases.js'; interface Module { path: string; diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/get-imported-modules.spec.ts b/packages/react-email/src/utils/preview/hot-reloading/get-imported-modules.spec.ts similarity index 96% rename from packages/react-email/src/cli/utils/preview/hot-reloading/get-imported-modules.spec.ts rename to packages/react-email/src/utils/preview/hot-reloading/get-imported-modules.spec.ts index 8e7d545271..f98bbb2808 100644 --- a/packages/react-email/src/cli/utils/preview/hot-reloading/get-imported-modules.spec.ts +++ b/packages/react-email/src/utils/preview/hot-reloading/get-imported-modules.spec.ts @@ -1,5 +1,5 @@ import { promises as fs } from 'node:fs'; -import { getImportedModules } from './get-imported-modules'; +import { getImportedModules } from './get-imported-modules.js'; vi.mock('@babel/traverse', async () => { const traverse = await vi.importActual('@babel/traverse'); @@ -12,7 +12,7 @@ describe('getImportedModules()', () => { expect(getImportedModules(contents)).toEqual([ 'node:fs', - './get-imported-modules', + './get-imported-modules.js', ]); }); diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/get-imported-modules.ts b/packages/react-email/src/utils/preview/hot-reloading/get-imported-modules.ts similarity index 87% rename from packages/react-email/src/cli/utils/preview/hot-reloading/get-imported-modules.ts rename to packages/react-email/src/utils/preview/hot-reloading/get-imported-modules.ts index 554abc3bf2..fa08eb4880 100644 --- a/packages/react-email/src/cli/utils/preview/hot-reloading/get-imported-modules.ts +++ b/packages/react-email/src/utils/preview/hot-reloading/get-imported-modules.ts @@ -7,9 +7,7 @@ const traverse = // script's use of tsx typeof traverseModule === 'function' ? traverseModule - : // @ts-expect-error This is fine since the default export is wrapped in a - // default function. The problem is that babel/traverse is not ESM. - traverseModule.default; + : traverseModule.default; export const getImportedModules = (contents: string) => { const importedPaths: string[] = []; diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/resolve-path-aliases.spec.ts b/packages/react-email/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts similarity index 77% rename from packages/react-email/src/cli/utils/preview/hot-reloading/resolve-path-aliases.spec.ts rename to packages/react-email/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts index 8e50824db2..be7635ec86 100644 --- a/packages/react-email/src/cli/utils/preview/hot-reloading/resolve-path-aliases.spec.ts +++ b/packages/react-email/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts @@ -1,5 +1,5 @@ import path from 'node:path'; -import { resolvePathAliases } from './resolve-path-aliases'; +import { resolvePathAliases } from './resolve-path-aliases.js'; test('resolveImports()', async () => { expect( diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/resolve-path-aliases.ts b/packages/react-email/src/utils/preview/hot-reloading/resolve-path-aliases.ts similarity index 100% rename from packages/react-email/src/cli/utils/preview/hot-reloading/resolve-path-aliases.ts rename to packages/react-email/src/utils/preview/hot-reloading/resolve-path-aliases.ts diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts b/packages/react-email/src/utils/preview/hot-reloading/setup-hot-reloading.ts similarity index 97% rename from packages/react-email/src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts rename to packages/react-email/src/utils/preview/hot-reloading/setup-hot-reloading.ts index d701cb339e..d6fb95e2bc 100644 --- a/packages/react-email/src/cli/utils/preview/hot-reloading/setup-hot-reloading.ts +++ b/packages/react-email/src/utils/preview/hot-reloading/setup-hot-reloading.ts @@ -3,8 +3,8 @@ import path from 'node:path'; import { watch } from 'chokidar'; import debounce from 'debounce'; import { type Socket, Server as SocketServer } from 'socket.io'; -import type { HotReloadChange } from '../../../../utils/types/hot-reload-change'; -import { createDependencyGraph } from './create-dependency-graph'; +import type { HotReloadChange } from '../../types/hot-reload-change.js'; +import { createDependencyGraph } from './create-dependency-graph.js'; export const setupHotreloading = async ( devServer: http.Server, diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/test/some-file.ts b/packages/react-email/src/utils/preview/hot-reloading/test/some-file.ts similarity index 100% rename from packages/react-email/src/cli/utils/preview/hot-reloading/test/some-file.ts rename to packages/react-email/src/utils/preview/hot-reloading/test/some-file.ts diff --git a/packages/react-email/src/cli/utils/preview/hot-reloading/test/tsconfig.json b/packages/react-email/src/utils/preview/hot-reloading/test/tsconfig.json similarity index 100% rename from packages/react-email/src/cli/utils/preview/hot-reloading/test/tsconfig.json rename to packages/react-email/src/utils/preview/hot-reloading/test/tsconfig.json diff --git a/packages/react-email/src/utils/preview/index.ts b/packages/react-email/src/utils/preview/index.ts new file mode 100644 index 0000000000..508fedb6a9 --- /dev/null +++ b/packages/react-email/src/utils/preview/index.ts @@ -0,0 +1,2 @@ +export * from './hot-reloading/setup-hot-reloading.js'; +export * from './start-dev-server.js'; diff --git a/packages/react-email/src/cli/utils/preview/serve-static-file.ts b/packages/react-email/src/utils/preview/serve-static-file.ts similarity index 100% rename from packages/react-email/src/cli/utils/preview/serve-static-file.ts rename to packages/react-email/src/utils/preview/serve-static-file.ts diff --git a/packages/react-email/src/cli/utils/preview/start-dev-server.ts b/packages/react-email/src/utils/preview/start-dev-server.ts similarity index 87% rename from packages/react-email/src/cli/utils/preview/start-dev-server.ts rename to packages/react-email/src/utils/preview/start-dev-server.ts index 41e420aa04..192d4f7d01 100644 --- a/packages/react-email/src/cli/utils/preview/start-dev-server.ts +++ b/packages/react-email/src/utils/preview/start-dev-server.ts @@ -2,13 +2,14 @@ import http from 'node:http'; import path from 'node:path'; import url from 'node:url'; import chalk from 'chalk'; +import { createJiti } from 'jiti'; import logSymbols from 'log-symbols'; -import next from 'next'; import ora from 'ora'; -import packageJson from '../../../../package.json'; -import { registerSpinnerAutostopping } from '../../../utils/register-spinner-autostopping'; -import { getEnvVariablesForPreviewApp } from './get-env-variables-for-preview-app'; -import { serveStaticFile } from './serve-static-file'; +import { registerSpinnerAutostopping } from '../../utils/register-spinner-autostopping.js'; +import { getPreviewServerLocation } from '../get-preview-server-location.js'; +import { packageJson } from '../packageJson.js'; +import { getEnvVariablesForPreviewApp } from './get-env-variables-for-preview-app.js'; +import { serveStaticFile } from './serve-static-file.js'; let devServer: http.Server | undefined; @@ -29,13 +30,7 @@ const safeAsyncServerListen = (server: http.Server, port: number) => { const filename = url.fileURLToPath(import.meta.url); const dirname = path.dirname(filename); -export const isDev = !filename.endsWith(path.join('cli', 'index.mjs')); -export const cliPackageLocation = isDev - ? path.resolve(dirname, '../../../..') - : path.resolve(dirname, '../..'); -export const previewServerLocation = isDev - ? path.resolve(dirname, '../../../..') - : path.resolve(dirname, '../preview'); +export const isDev = !dirname.includes('dist'); export const startDevServer = async ( emailsDirRelativePath: string, @@ -50,6 +45,12 @@ export const startDevServer = async ( process.exit(1); } + const previewServerLocation = await getPreviewServerLocation(); + const previewServer = createJiti(previewServerLocation); + + const { default: next } = + await previewServer.import('next'); + devServer = http.createServer((req, res) => { if (!req.url) { res.end(404); @@ -191,13 +192,17 @@ const makeExitHandler = | { shouldKillProcess: false } | { shouldKillProcess: true; killWithErrorCode: boolean }, ) => - (_codeOrSignal: number | NodeJS.Signals) => { + (codeSignalOrError: number | NodeJS.Signals | Error) => { if (typeof devServer !== 'undefined') { - console.log('\n shutting down dev server'); + console.log('\nshutting down dev server'); devServer.close(); devServer = undefined; } + if (codeSignalOrError instanceof Error) { + console.error(codeSignalOrError); + } + if (options?.shouldKillProcess) { process.exit(options.killWithErrorCode ? 1 : 0); } diff --git a/packages/react-email/src/utils/tree.spec.ts b/packages/react-email/src/utils/tree.spec.ts new file mode 100644 index 0000000000..a265e0a761 --- /dev/null +++ b/packages/react-email/src/utils/tree.spec.ts @@ -0,0 +1,5 @@ +import { tree } from './tree.js'; + +test('tree(__dirname, 2)', async () => { + expect(await tree(__dirname, 2)).toMatchSnapshot(); +}); diff --git a/packages/react-email/src/cli/utils/tree.ts b/packages/react-email/src/utils/tree.ts similarity index 100% rename from packages/react-email/src/cli/utils/tree.ts rename to packages/react-email/src/utils/tree.ts diff --git a/packages/react-email/src/utils/types/hot-reload-change.ts b/packages/react-email/src/utils/types/hot-reload-change.ts index cad7b9ebbf..8e4d308a91 100644 --- a/packages/react-email/src/utils/types/hot-reload-change.ts +++ b/packages/react-email/src/utils/types/hot-reload-change.ts @@ -1,4 +1,4 @@ -import type { HotReloadEvent } from './hot-reload-event'; +import type { HotReloadEvent } from './hot-reload-event.js'; export interface HotReloadChange { filename: string; diff --git a/packages/react-email/src/utils/types/hot-reload-event.ts b/packages/react-email/src/utils/types/hot-reload-event.ts index 92ed7cb358..509a17e514 100644 --- a/packages/react-email/src/utils/types/hot-reload-event.ts +++ b/packages/react-email/src/utils/types/hot-reload-event.ts @@ -1,3 +1,3 @@ -import type { EventName } from 'chokidar/handler'; +import type { EventName } from 'chokidar/handler.js'; export type HotReloadEvent = EventName; diff --git a/packages/react-email/tsconfig.json b/packages/react-email/tsconfig.json index 90c21e09c8..b10c1be9e0 100644 --- a/packages/react-email/tsconfig.json +++ b/packages/react-email/tsconfig.json @@ -8,7 +8,7 @@ "forceConsistentCasingInFileNames": true, "inlineSources": false, "isolatedModules": true, - "moduleResolution": "node", + "moduleResolution": "nodenext", "noUnusedLocals": false, "noUnusedParameters": false, "preserveWatchOutput": true, @@ -28,18 +28,12 @@ "noEmit": true, "strict": false, "target": "ESNext", - "module": "ESNext", + "module": "NodeNext", "noUncheckedIndexedAccess": true, "resolveJsonModule": true, "types": ["vitest/globals"], "outDir": "dist" }, - "include": [ - "next-env.d.ts", - "tailwind-internals.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [".next", "dist", "node_modules"] + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] } diff --git a/packages/react-email/tsup.config.ts b/packages/react-email/tsup.config.ts index 44d6d43185..e1236ac842 100644 --- a/packages/react-email/tsup.config.ts +++ b/packages/react-email/tsup.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ dts: false, - entry: ['./src/cli/index.ts'], + entry: ['./src/index.ts'], format: ['esm'], - outDir: 'dist/cli', + outDir: 'dist', }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1360dbb398..0a11fc9c39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: specifier: workspace:* version: link:../../packages/react-email devDependencies: + '@react-email/preview-server': + specifier: workspace:* + version: link:../../packages/preview-server '@types/react': specifier: ^19.0.1 version: 19.0.1 @@ -73,7 +76,7 @@ importers: specifier: ^19.0.1 version: 19.0.1 next: - specifier: 15.3.1 + specifier: ^15.2.4 version: 15.3.1(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) tsx: specifier: 4.19.3 @@ -612,57 +615,17 @@ importers: specifier: 5.8.3 version: 5.8.3 - packages/react-email: + packages/preview-server: dependencies: + '@babel/core': + specifier: 7.26.10 + version: 7.26.10 '@babel/parser': specifier: ^7.27.0 version: 7.27.0 '@babel/traverse': specifier: ^7.27.0 version: 7.27.0 - chalk: - specifier: ^5.0.0 - version: 5.4.0 - chokidar: - specifier: ^4.0.3 - version: 4.0.3 - commander: - specifier: ^13.0.0 - version: 13.1.0 - debounce: - specifier: ^2.0.0 - version: 2.0.0 - esbuild: - specifier: ^0.25.0 - version: 0.25.0 - glob: - specifier: ^11.0.0 - version: 11.0.1 - log-symbols: - specifier: ^7.0.0 - version: 7.0.0 - mime-types: - specifier: ^3.0.0 - version: 3.0.1 - next: - specifier: ^15.3.1 - version: 15.3.1(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - normalize-path: - specifier: ^3.0.0 - version: 3.0.0 - ora: - specifier: ^8.0.0 - version: 8.2.0 - socket.io: - specifier: ^4.8.1 - version: 4.8.1 - tsconfig-paths: - specifier: 4.2.0 - version: 4.2.0 - devDependencies: - '@babel/core': - specifier: 7.26.10 - version: 7.26.10 '@lottiefiles/dotlottie-react': specifier: 0.13.3 version: 0.13.3(react@19.0.0) @@ -690,24 +653,6 @@ importers: '@radix-ui/react-tooltip': specifier: 1.2.3 version: 1.2.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-email/components': - specifier: workspace:* - version: link:../components - '@swc/core': - specifier: 1.11.21 - version: 1.11.21 - '@types/babel__core': - specifier: 7.20.5 - version: 7.20.5 - '@types/babel__traverse': - specifier: 7.20.7 - version: 7.20.7 - '@types/fs-extra': - specifier: 11.0.1 - version: 11.0.1 - '@types/mime-types': - specifier: 2.1.4 - version: 2.1.4 '@types/node': specifier: 22.14.1 version: 22.14.1 @@ -726,27 +671,36 @@ importers: autoprefixer: specifier: 10.4.21 version: 10.4.21(postcss@8.5.3) + chalk: + specifier: ^4.1.2 + version: 4.1.2 clsx: specifier: 2.1.1 version: 2.1.1 + esbuild: + specifier: ^0.25.0 + version: 0.25.0 framer-motion: specifier: 12.7.5 version: 12.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - jiti: - specifier: 2.4.2 - version: 2.4.2 json5: specifier: 2.2.3 version: 2.2.3 + log-symbols: + specifier: ^4.1.0 + version: 4.1.0 module-punycode: specifier: npm:punycode@2.3.1 version: punycode@2.3.1 + next: + specifier: ^15.2.4 + version: 15.3.1(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) node-html-parser: specifier: 7.0.1 version: 7.0.1 - postcss: - specifier: 8.5.3 - version: 8.5.3 + ora: + specifier: ^5.4.1 + version: 5.4.1 pretty-bytes: specifier: 6.1.1 version: 6.1.1 @@ -783,6 +737,113 @@ importers: tailwindcss: specifier: 3.4.0 version: 3.4.0 + use-debounce: + specifier: 10.0.4 + version: 10.0.4(react@19.0.0) + zod: + specifier: 3.24.3 + version: 3.24.3 + devDependencies: + '@react-email/components': + specifier: workspace:* + version: link:../components + '@types/babel__core': + specifier: 7.20.5 + version: 7.20.5 + '@types/babel__traverse': + specifier: 7.20.7 + version: 7.20.7 + '@types/fs-extra': + specifier: 11.0.1 + version: 11.0.1 + '@types/mime-types': + specifier: 2.1.4 + version: 2.1.4 + postcss: + specifier: 8.5.3 + version: 8.5.3 + typescript: + specifier: 5.8.3 + version: 5.8.3 + + packages/react-email: + dependencies: + '@babel/parser': + specifier: ^7.27.0 + version: 7.27.0 + '@babel/traverse': + specifier: ^7.27.0 + version: 7.27.0 + chalk: + specifier: ^5.0.0 + version: 5.4.0 + chokidar: + specifier: ^4.0.3 + version: 4.0.3 + commander: + specifier: ^13.0.0 + version: 13.1.0 + debounce: + specifier: ^2.0.0 + version: 2.0.0 + esbuild: + specifier: ^0.25.0 + version: 0.25.0 + glob: + specifier: ^11.0.0 + version: 11.0.1 + jiti: + specifier: 2.4.2 + version: 2.4.2 + log-symbols: + specifier: ^7.0.0 + version: 7.0.0 + mime-types: + specifier: ^3.0.0 + version: 3.0.1 + normalize-path: + specifier: ^3.0.0 + version: 3.0.0 + nypm: + specifier: 0.6.0 + version: 0.6.0 + ora: + specifier: ^8.0.0 + version: 8.2.0 + prompts: + specifier: 2.4.2 + version: 2.4.2 + socket.io: + specifier: ^4.8.1 + version: 4.8.1 + tsconfig-paths: + specifier: 4.2.0 + version: 4.2.0 + devDependencies: + '@react-email/components': + specifier: workspace:* + version: link:../components + '@types/babel__core': + specifier: 7.20.5 + version: 7.20.5 + '@types/babel__traverse': + specifier: 7.20.7 + version: 7.20.7 + '@types/mime-types': + specifier: 2.1.4 + version: 2.1.4 + '@types/prompts': + specifier: 2.4.9 + version: 2.4.9 + next: + specifier: ^15.3.1 + version: 15.3.1(@babel/core@7.26.10)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) tsup: specifier: 8.4.0 version: 8.4.0(@microsoft/api-extractor@7.52.4(@types/node@22.14.1))(@swc/core@1.11.21)(jiti@2.4.2)(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.3)(yaml@2.6.1) @@ -792,12 +853,6 @@ importers: typescript: specifier: 5.8.3 version: 5.8.3 - use-debounce: - specifier: 10.0.4 - version: 10.0.4(react@19.0.0) - zod: - specifier: 3.24.3 - version: 3.24.3 packages/render: dependencies: @@ -1008,10 +1063,6 @@ packages: resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.10': - resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.26.3': resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} engines: {node: '>=6.9.0'} @@ -1070,10 +1121,6 @@ packages: resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -1175,12 +1222,8 @@ packages: resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.10': - resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.3': - resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} '@babel/types@7.27.1': @@ -3708,6 +3751,9 @@ packages: '@types/prismjs@1.26.5': resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + '@types/prompts@2.4.9': + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + '@types/react-dom@19.0.1': resolution: {integrity: sha512-hljHij7MpWPKF6u5vojuyfV0YA4YURsQG7KT6SzV0Zs2BXAtgdTxG6A229Ub/xiWV4w/7JL8fi6aAyjshH4meA==} @@ -4282,6 +4328,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -5785,6 +5834,10 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} @@ -6369,6 +6422,11 @@ packages: nwsapi@2.2.20: resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nypm@0.6.0: + resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -6744,6 +6802,10 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} @@ -7229,6 +7291,9 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -8305,7 +8370,7 @@ snapshots: '@babel/parser': 7.27.0 '@babel/template': 7.25.9 '@babel/traverse': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 convert-source-map: 2.0.0 debug: 4.4.0 gensync: 1.0.0-beta.2 @@ -8317,15 +8382,15 @@ snapshots: '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.10 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.1 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) '@babel/helpers': 7.26.10 '@babel/parser': 7.27.0 - '@babel/template': 7.26.9 + '@babel/template': 7.27.1 '@babel/traverse': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.1 convert-source-map: 2.0.0 debug: 4.4.0 gensync: 1.0.0-beta.2 @@ -8334,18 +8399,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.10': - dependencies: - '@babel/parser': 7.27.0 - '@babel/types': 7.26.10 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - '@babel/generator@7.26.3': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 @@ -8401,7 +8458,7 @@ snapshots: '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 transitivePeerDependencies: - supports-color @@ -8445,8 +8502,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.25.9': {} - '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.25.9': {} @@ -8458,12 +8513,12 @@ snapshots: '@babel/helpers@7.26.0': dependencies: '@babel/template': 7.26.9 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@babel/helpers@7.26.10': dependencies: - '@babel/template': 7.26.9 - '@babel/types': 7.26.10 + '@babel/template': 7.27.1 + '@babel/types': 7.27.1 '@babel/parser@7.24.5': dependencies: @@ -8535,13 +8590,13 @@ snapshots: dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@babel/template@7.26.9': dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@babel/template@7.27.1': dependencies: @@ -8561,15 +8616,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.26.10': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - - '@babel/types@7.26.3': + '@babel/types@7.27.0': dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 '@babel/types@7.27.1': dependencies: @@ -11018,6 +11068,7 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.11.21 '@swc/core-win32-ia32-msvc': 1.11.21 '@swc/core-win32-x64-msvc': 1.11.21 + optional: true '@swc/core@1.3.101(@swc/helpers@0.5.15)': dependencies: @@ -11054,6 +11105,7 @@ snapshots: '@swc/types@0.1.21': dependencies: '@swc/counter': 0.1.3 + optional: true '@szmarczak/http-timer@5.0.1': dependencies: @@ -11070,23 +11122,23 @@ snapshots: '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.26.3 + '@babel/types': 7.27.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.27.0 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@types/cookie@0.4.1': {} @@ -11180,6 +11232,11 @@ snapshots: '@types/prismjs@1.26.5': {} + '@types/prompts@2.4.9': + dependencies: + '@types/node': 22.14.1 + kleur: 3.0.3 + '@types/react-dom@19.0.1': dependencies: '@types/react': 19.0.1 @@ -11839,6 +11896,10 @@ snapshots: ci-info@3.9.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + classnames@2.5.1: {} clean-stack@4.2.0: @@ -13618,6 +13679,8 @@ snapshots: kind-of@6.0.3: {} + kleur@3.0.3: {} + kolorist@1.8.0: {} lcm@0.0.3: @@ -14462,6 +14525,14 @@ snapshots: nwsapi@2.2.20: {} + nypm@0.6.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.1.0 + tinyexec: 0.3.2 + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -14640,7 +14711,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.26.2 + '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -14846,6 +14917,11 @@ snapshots: progress@2.0.3: {} + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + property-information@6.5.0: {} property-information@7.0.0: {} @@ -15623,6 +15699,8 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sisteransi@1.0.5: {} + slash@3.0.0: {} smart-buffer@4.2.0: {} @@ -15940,7 +16018,7 @@ snapshots: postcss-js: 4.0.1(postcss@8.5.3) postcss-load-config: 4.0.2(postcss@8.5.3) postcss-nested: 6.2.0(postcss@8.5.3) - postcss-selector-parser: 6.0.16 + postcss-selector-parser: 6.1.2 resolve: 1.22.9 sucrase: 3.35.0 transitivePeerDependencies: diff --git a/turbo.json b/turbo.json index a10d59c6fc..db6eb7ce76 100644 --- a/turbo.json +++ b/turbo.json @@ -26,6 +26,10 @@ ], "outputs": [".next/**", "!.next/cache/**"] }, + "@react-email/preview-server#build": { + "dependsOn": ["^build"], + "outputs": [".next/**", "!.next/cache/**"] + }, "demo#build": { "dependsOn": ["^build", "^postbuild"], "outputs": [".react-email/**", "!.react-email/.next/cache/**"]