feat(preview-server): isolated development workflow (#2249)

This commit is contained in:
Gabriel Miranda
2025-07-04 11:21:26 -03:00
committed by GitHub
parent 2e9126f1b4
commit cdff581c8d
23 changed files with 688 additions and 100 deletions
+1
View File
@@ -23,6 +23,7 @@
"pkg-pr-new": "0.0.51",
"tsconfig": "workspace:*",
"tsup": "8.4.0",
"tsx": "4.19.3",
"turbo": "2.5.4",
"vite": "6.3.4",
"vitest": "3.2.3"
+2
View File
@@ -3,3 +3,5 @@ node_modules
# for testing
static
emails
.env*
+4 -2
View File
@@ -3,9 +3,11 @@
"version": "4.1.0-canary.12",
"description": "A live preview of your emails right in your browser.",
"scripts": {
"caniemail:fetch": "node ./scripts/fill-caniemail-data.mjs",
"build": "tsx ./scripts/build-preview-server.mts",
"caniemail:fetch": "tsx ./scripts/fill-caniemail-data.mts",
"clean": "rm -rf dist",
"build": "node ./scripts/build-preview-server.mjs",
"dev": "tsx ./scripts/dev.mts",
"dev:seed": "tsx ./scripts/seed.mts",
"test": "vitest run",
"test:watch": "vitest"
},
+35
View File
@@ -0,0 +1,35 @@
<div align="center"><strong>@react-email/preview-server</strong></div>
<div align="center">A live preview of your emails right in your browser.</div>
<br />
<div align="center">
<a href="https://react.email">Website</a>
<span> · </span>
<a href="https://github.com/resend/react-email">GitHub</a>
<span> · </span>
<a href="https://react.email/discord">Discord</a>
</div>
This package is used to store the preview server, it is also published and versioned so that it can be installed when the [CLI](../react-email) is being used.
## Development workflow
### 1. Seed email templates
```sh
pnpm dev:seed
```
This generates a boilerplate emails directory for you to work with. These files can also be modified as you see fit since they are not included in git.
### 2. Run development server
```sh
pnpm dev
```
This is somewhat equivalent to `next dev` and does not support hot reloading for email templates like the CLI does. It lets you work on the UI for the preview server mainly.
### 3. Open in your browser
Go to http://localhost:3000
+47
View File
@@ -0,0 +1,47 @@
import child_process from 'node:child_process';
import { promises as 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 previewServerRoot = path.resolve(dirname, '..');
const emailsDirectoryPath = path.join(previewServerRoot, 'emails');
const envPath = path.join(previewServerRoot, '.env.local');
await fs.writeFile(
envPath,
`EMAILS_DIR_RELATIVE_PATH=./emails
EMAILS_DIR_ABSOLUTE_PATH=${emailsDirectoryPath}
USER_PROJECT_LOCATION=${previewServerRoot}
NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT=true`,
'utf8',
);
const webServerProcess = child_process.spawn('next', ['dev'], {
cwd: previewServerRoot,
shell: true,
stdio: 'inherit',
});
webServerProcess.on('exit', async () => {
await fs.rm(envPath);
});
process.on('SIGINT', () => {
webServerProcess.kill('SIGINT');
});
process.on('SIGUSR1', () => {
webServerProcess.kill('SIGUSR1');
});
process.on('SIGUSR2', () => {
webServerProcess.kill('SIGUSR2');
});
process.on('uncaughtExceptionMonitor', () => {
webServerProcess.kill();
});
process.on('exit', () => {
webServerProcess.kill();
});
@@ -14,11 +14,6 @@ This could be happneing for the following reasons:
- You don't have internet connectivity
- Caniemail is down
- Caniemail changed from where to fetch their data from, which means we need to fix this. If this is the case, please open up an issue.`,
{
cause: {
responseFromCaniemail,
},
},
);
}
+21
View File
@@ -0,0 +1,21 @@
import { existsSync, promises as 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 previewServerRoot = path.resolve(dirname, '..');
const emailsDirectoryPath = path.join(previewServerRoot, 'emails');
const seedPath = path.join(dirname, './utils/default-seed/');
if (existsSync(emailsDirectoryPath)) {
console.info('Deleting previous emails directory');
await fs.rm(emailsDirectoryPath, { recursive: true, force: true });
}
console.info('Copying over the defalt seed to the emails directory');
await fs.cp(seedPath, emailsDirectoryPath, {
recursive: true,
});
@@ -0,0 +1,68 @@
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Row,
Tailwind,
Text,
} from '@react-email/components';
interface AccountConfirmationProps {
confirmLink: string;
expiryTime: string;
}
export default function AccountConfirmation({
confirmLink,
expiryTime,
}: AccountConfirmationProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>Confirm your React Email account</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center my-[48px] text-[32px]">
Welcome to React Email!
</Heading>
<Text>
Thank you for signing up! To complete your registration and start
using React Email, please confirm your email address.
</Text>
<Text className="mb-6">
Click the button below to verify your email. This link will expire
in {expiryTime}.
</Text>
<Row className="w-full">
<Column className="w-full">
<Button
href={confirmLink}
className="bg-cyan-300 text-[20px] font-bold text-[#404040] w-full text-center border border-solid border-cyan-900 py-[8px] rounded-[8px]"
>
Confirm Email
</Button>
</Column>
</Row>
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
AccountConfirmation.PreviewProps = {
confirmLink: 'https://react.email/confirm/123',
expiryTime: '24 hours',
} satisfies AccountConfirmationProps;
@@ -0,0 +1,71 @@
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Row,
Tailwind,
Text,
} from '@react-email/components';
interface ForgotPasswordProps {
resetLink: string;
expiryTime: string;
}
export default function ForgotPassword({
resetLink,
expiryTime,
}: ForgotPasswordProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>Reset your React Email password</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center my-[48px] text-[32px]">
Reset Your Password
</Heading>
<Text>
We received a request to reset your password for your React Email
account.
</Text>
<Text>
Click the button below to create a new password. This link will
expire in {expiryTime}.
</Text>
<Text className="mb-6">
If you didn't request this, you can safely ignore this email.
</Text>
<Row className="w-full">
<Column className="w-full">
<Button
href={resetLink}
className="bg-cyan-300 text-[20px] font-bold text-[#404040] w-full text-center border border-solid border-cyan-900 py-[8px] rounded-[8px]"
>
Reset Password
</Button>
</Column>
</Row>
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
ForgotPassword.PreviewProps = {
resetLink: 'https://react.email/reset-password/123',
expiryTime: '1 hour',
} satisfies ForgotPasswordProps;
@@ -0,0 +1,82 @@
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Row,
Tailwind,
Text,
} from '@react-email/components';
interface PaymentOverdueProps {
customerName: string;
amount: string;
dueDate: string;
invoiceLink: string;
}
export default function PaymentOverdue({
customerName,
amount,
dueDate,
invoiceLink,
}: PaymentOverdueProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>Payment of {amount} is overdue - Action required</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center my-[48px] text-[32px]">
Payment Overdue
</Heading>
<Text>Dear {customerName},</Text>
<Text>
We noticed that your payment of {amount} was due on {dueDate} and
has not been received yet.
</Text>
<Text>
To avoid any service interruption, please process your payment as
soon as possible.
</Text>
<Text className="mb-6">
You can view and pay your invoice by clicking the button below:
</Text>
<Row className="w-full">
<Column className="w-full">
<Button
href={invoiceLink}
className="bg-cyan-300 text-[20px] font-bold text-[#404040] w-full text-center border border-solid border-cyan-900 py-[8px] rounded-[8px]"
>
Pay Now
</Button>
</Column>
</Row>
<Text className="mt-6">
If you have already made this payment, please disregard this
message.
</Text>
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
PaymentOverdue.PreviewProps = {
customerName: 'Jane Smith',
amount: '$99.00',
dueDate: 'June 1st, 2024',
invoiceLink: 'https://react.email/invoice/123',
} satisfies PaymentOverdueProps;
@@ -0,0 +1,78 @@
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Row,
Tailwind,
Text,
} from '@react-email/components';
interface TeamInviteProps {
inviterName: string;
teamName: string;
inviteLink: string;
role: string;
}
export default function TeamInvite({
inviterName,
teamName,
inviteLink,
role,
}: TeamInviteProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>
{inviterName} has invited you to join {teamName} on React Email
</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center my-[48px] text-[32px]">
You've been invited!
</Heading>
<Text>
{inviterName} has invited you to join {teamName} as a {role}.
</Text>
<Text>
React Email helps teams collaborate on email templates and manage
their email campaigns effectively.
</Text>
<Text className="mb-6">
Click the button below to accept the invitation and get started:
</Text>
<Row className="w-full">
<Column className="w-full">
<Button
href={inviteLink}
className="bg-cyan-300 text-[20px] font-bold text-[#404040] w-full text-center border border-solid border-cyan-900 py-[8px] rounded-[8px]"
>
Accept Invitation
</Button>
</Column>
</Row>
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
TeamInvite.PreviewProps = {
inviterName: 'John Doe',
teamName: 'Marketing Team',
inviteLink: 'https://react.email/join/team/123',
role: 'Editor',
} satisfies TeamInviteProps;
@@ -0,0 +1,89 @@
import {
Body,
CodeBlock,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Tailwind,
Text,
vesper,
} from '@react-email/components';
interface WebhooksFailedProps {
date: string;
error: {
error: string;
timestamp: string;
webhookId: string;
details: {
reason: string;
retryCount: number;
lastAttempt: string;
};
};
}
export default function WebhooksFailed({ date, error }: WebhooksFailedProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>Some webhooks failed to deliver at {date}</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center text-[32px]">
Failed webhook attempt
</Heading>
<Text>
We encountered an issue with some of our webhooks. Please check
the logs for more details. Here is the data for the error:
</Text>
<CodeBlock
code={JSON.stringify(error, null, 2)}
language="json"
theme={vesper}
style={{
padding: '16px',
borderRadius: '8px',
}}
lineNumbers
/>
<Text className="mb-6">
If you have any questions or need assistance, please reach out to
us at{' '}
<a
href="mailto:support@react.email"
className="text-cyan-300 underline"
>
support@react.email
</a>
.
</Text>
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
WebhooksFailed.PreviewProps = {
date: 'June 4th, 202',
error: {
error: 'Webhook delivery failed',
timestamp: '2023-10-01T12:00:00Z',
webhookId: 'wh_1234567890',
details: {
reason: 'Network error',
retryCount: 3,
lastAttempt: '2023-10-01T12:05:00Z',
},
},
} satisfies WebhooksFailedProps;
@@ -0,0 +1,78 @@
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Row,
Tailwind,
Text,
} from '@react-email/components';
interface FeedbackRequestProps {
name: string;
}
export default function FeedbackRequest({ name }: FeedbackRequestProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>
Thank you for using our service. We would love to hear your
feedback.
</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center my-[48px] text-[32px]">
Hello {name},
</Heading>
<Text>
Thank you for using our service. We would love to hear your
thoughts and suggestions.
</Text>
<Text>
We work hard to improve our service and your feedback is crucial
for us to understand what we are doing well and where we can
improve.
</Text>
<Text className="mb-6">
If you have any questions about the service or need assistance,
please feel free to reach out to us at{' '}
<a
href="mailto:support@react.email"
className="text-cyan-300 underline"
>
support@react.email
</a>
.
</Text>
<Row className="w-full">
<Column className="w-full">
<Button
href="https://react.email"
className="bg-cyan-300 text-[20px] font-bold text-[#404040] w-full text-center border border-solid border-cyan-900 py-[8px] rounded-[8px]"
>
Give Feedback
</Button>
</Column>
</Row>
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
FeedbackRequest.PreviewProps = {
name: 'user',
} satisfies FeedbackRequestProps;
@@ -0,0 +1,98 @@
import {
Body,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components';
interface ChangelogProps {
receiver: string;
changes: Partial<Record<'features' | 'fixes' | 'improvements', string[]>>;
date: string;
}
interface ChangesProps {
title: React.ReactNode;
content: string[];
}
function Changes({ title, content }: ChangesProps) {
return (
<Section>
<Heading as="h2" className="my-[12px]">
{title}
</Heading>
<ul className="list-disc pl-6">
{content.map((feature, index) => (
<li key={index} className="mb-2">
{feature}
</li>
))}
</ul>
</Section>
);
}
export default function Changelog({ receiver, date, changes }: ChangelogProps) {
return (
<Html>
<Head />
<Tailwind>
<Body className="bg-black text-white">
<Preview>
The changes as of {date} particularly tailored to you
</Preview>
<Container className="mx-auto">
<Heading className="font-bold text-center my-[48px] text-[32px]">
Hello {receiver},
</Heading>
<Text>
We've been hard at work making improvements to our service, and we
wanted to share the latest changes as of{' '}
<span className="font-bold">{date}</span> with you.
</Text>
{changes.features && changes.features.length > 0 ? (
<Changes title="Features" content={changes.features} />
) : null}
{changes.improvements && changes.improvements.length > 0 ? (
<Changes title="Improvements" content={changes.improvements} />
) : null}
{changes.fixes && changes.fixes.length > 0 ? (
<Changes title="Fixes" content={changes.fixes} />
) : null}
<Text className="mt-6">- React Email team</Text>
<Hr style={{ borderTopColor: '#404040' }} />
<Text className="text-[#606060] font-bold">
React Email, 999 React St, Email City, EC 12345
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
Changelog.PreviewProps = {
receiver: 'user',
changes: {
features: [
'Added a new feature to enhance user experience',
'Introduced a dark mode option for better accessibility',
],
fixes: [
'Fixed a bug that caused the app to crash on startup',
'Resolved an issue with email notifications not being sent',
],
improvements: [
'Improved performance of the application by optimizing database queries',
'Enhanced security measures to protect user data',
],
},
date: 'June 4th, 2025',
} satisfies ChangelogProps;
@@ -1,7 +1,7 @@
'use client';
import { createContext, useContext, useState } from 'react';
import { getEmailsDirectoryMetadataAction } from '../actions/get-emails-directory-metadata-action';
import { isBuilding } from '../app/env';
import { isBuilding, isPreviewDevelopment } from '../app/env';
import { useHotreload } from '../hooks/use-hot-reload';
import type { EmailsDirectory } from '../utils/get-emails-directory-metadata';
@@ -31,7 +31,7 @@ export const EmailsProvider = (props: {
const [emailsDirectoryMetadata, setEmailsDirectoryMetadata] =
useState<EmailsDirectory>(props.initialEmailsDirectoryMetadata);
if (!isBuilding) {
if (!isBuilding && !isPreviewDevelopment) {
// biome-ignore lint/correctness/useHookAtTopLevel: this will not change on runtime so it doesn't violate the rules of hooks
useHotreload(async () => {
const metadata = await getEmailsDirectoryMetadataAction(
@@ -5,7 +5,7 @@ import type {
EmailRenderingResult,
RenderedEmailMetadata,
} from '../actions/render-email-by-path';
import { isBuilding } from '../app/env';
import { isBuilding, isPreviewDevelopment } from '../app/env';
import { useEmailRenderingResult } from '../hooks/use-email-rendering-result';
import { useHotreload } from '../hooks/use-hot-reload';
import { useRenderingMetadata } from '../hooks/use-rendering-metadata';
@@ -49,7 +49,7 @@ export const PreviewProvider = ({
serverRenderingResult,
);
if (!isBuilding) {
if (!isBuilding && !isPreviewDevelopment) {
// biome-ignore lint/correctness/useHookAtTopLevel: this will not change on runtime so it doesn't violate the rules of hooks
useHotreload((changes) => {
const changeForThisEmail = changes.find((change) =>
@@ -4,7 +4,7 @@ import {
type EmailRenderingResult,
renderEmailByPath,
} from '../actions/render-email-by-path';
import { isBuilding } from '../app/env';
import { isBuilding, isPreviewDevelopment } from '../app/env';
import { useEmails } from '../contexts/emails';
import { containsEmailTemplate } from '../utils/contains-email-template';
import { useHotreload } from './use-hot-reload';
@@ -19,7 +19,7 @@ export const useEmailRenderingResult = (
const { emailsDirectoryMetadata } = useEmails();
if (!isBuilding) {
if (!isBuilding && !isPreviewDevelopment) {
// biome-ignore lint/correctness/useHookAtTopLevel: This is fine since isBuilding does not change at runtime
useHotreload(async (changes) => {
for await (const change of changes) {
@@ -1,5 +1,4 @@
import path from 'node:path';
import { isDev } from './start-dev-server.js';
export const getEnvVariablesForPreviewApp = (
relativePathToEmailsDirectory: string,
@@ -9,6 +8,5 @@ export const getEnvVariablesForPreviewApp = (
EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
USER_PROJECT_LOCATION: cwd,
NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT: isDev ? 'true' : 'false',
} as const;
};
@@ -44,52 +44,21 @@ test('createDependencyGraph()', async () => {
};
const initialDependencyGraph = convertPathsToAbsolute({
'../../../../package.json': {
dependencyPaths: [],
dependentPaths: ['../../packageJson.ts'],
moduleDependencies: [],
path: '../../../../package.json',
},
'create-dependency-graph.ts': {
path: 'create-dependency-graph.ts',
dependencyPaths: [
'../start-dev-server.ts',
'get-imported-modules.ts',
'resolve-path-aliases.ts',
],
dependencyPaths: ['get-imported-modules.ts', 'resolve-path-aliases.ts'],
dependentPaths: [
'create-dependency-graph.spec.ts',
'setup-hot-reloading.ts',
],
moduleDependencies: ['node:fs', 'node:path', 'chokidar/handler.js'],
},
'../../get-preview-server-location.ts': {
dependencyPaths: ['../../packageJson.ts'],
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: [
'../../get-preview-server-location.ts',
'../../preview/start-dev-server.ts',
],
moduleDependencies: [],
path: '../../packageJson.ts',
},
'create-dependency-graph.spec.ts': {
path: 'create-dependency-graph.spec.ts',
dependencyPaths: ['create-dependency-graph.ts'],
dependentPaths: [],
moduleDependencies: ['node:fs', 'node:path'],
},
'../get-env-variables-for-preview-app.ts': {
dependencyPaths: ['../../preview/start-dev-server.ts'],
dependentPaths: ['../../preview/start-dev-server.ts'],
moduleDependencies: ['node:path'],
path: '../../preview/get-env-variables-for-preview-app.ts',
},
'./test/some-file.ts': {
dependencyPaths: [],
dependentPaths: [],
@@ -141,47 +110,6 @@ test('createDependencyGraph()', async () => {
'socket.io',
],
},
'../start-dev-server.ts': {
dependencyPaths: [
'../../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: [
'../../preview/get-env-variables-for-preview-app.ts',
'create-dependency-graph.ts',
],
moduleDependencies: [
'node:http',
'node:path',
'node:url',
'chalk',
'jiti',
'log-symbols',
'ora',
],
},
'../../preview/serve-static-file.ts': {
dependencyPaths: [],
dependentPaths: ['../../preview/start-dev-server.ts'],
moduleDependencies: [
'node:fs',
'node:http',
'node:path',
'node:url',
'mime-types',
],
path: '../../preview/serve-static-file.ts',
},
'../../register-spinner-autostopping.ts': {
dependencyPaths: [],
dependentPaths: ['../../preview/start-dev-server.ts'],
moduleDependencies: ['log-symbols', 'ora'],
path: '../../register-spinner-autostopping.ts',
},
'../../types/hot-reload-event.ts': {
dependencyPaths: [],
dependentPaths: ['../../types/hot-reload-change.ts'],
@@ -1,7 +1,6 @@
import { existsSync, promises as fs, statSync } from 'node:fs';
import path from 'node:path';
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';
@@ -134,8 +133,7 @@ export const createDependencyGraph = async (directory: string) => {
);
if (pathWithExtension) {
pathToDependencyFromDirectory = pathWithExtension;
} else if (isDev) {
// only warn about this on development as it is probably going to be irrelevant otherwise
} else {
console.warn(
`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`,
);
@@ -157,8 +155,7 @@ export const createDependencyGraph = async (directory: string) => {
if (pathWithEnsuredExtension) {
pathToDependencyFromDirectory = pathWithEnsuredExtension;
} else if (isDev) {
// only warn about this on development as it is probably going to be irrelevant otherwise
} else {
console.warn(
`Could not find file at ${pathToDependencyFromDirectory}`,
);
@@ -27,11 +27,6 @@ const safeAsyncServerListen = (server: http.Server, port: number) => {
});
};
const filename = url.fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
export const isDev = !dirname.includes('dist');
export const startDevServer = async (
emailsDirRelativePath: string,
staticBaseDirRelativePath: string,
@@ -143,7 +138,7 @@ export const startDevServer = async (
const app = next({
// passing in env here does not get the environment variables there
dev: isDev,
dev: false,
conf: {
images: {
// This is to avoid the warning with sharp
+4 -1
View File
@@ -41,6 +41,9 @@ importers:
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)
tsx:
specifier: 4.19.3
version: 4.19.3
turbo:
specifier: 2.5.4
version: 2.5.4
@@ -17006,7 +17009,7 @@ snapshots:
picomatch: 4.0.2
postcss: 8.5.3
rollup: 4.39.0
tinyglobby: 0.2.13
tinyglobby: 0.2.14
optionalDependencies:
'@types/node': 22.14.1
fsevents: 2.3.3