Skip to content

Commit 133fce7

Browse files
committed
fix(preview): normalize component & import paths on Windows
1 parent d2129fe commit 133fce7

File tree

8 files changed

+44
-104
lines changed

8 files changed

+44
-104
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"editor.tabSize": 4,
3+
"editor.insertSpaces": false,
4+
"editor.detectIndentation": false
5+
}

src/lib/preview/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const emailList = ({
5353
} catch (err) {
5454
throw new Error(
5555
'Could not determine the root path of your project. Please pass in the root param manually using process.cwd() or an absolute path.\nOriginal error: ' +
56-
err
56+
err
5757
);
5858
}
5959
}
@@ -66,7 +66,9 @@ export const emailList = ({
6666
return { files: null, path: emailPath };
6767
}
6868

69-
const files = createEmailComponentList(emailPath, getFiles(fullPath));
69+
// Use the absolute folder path as the root when creating the component list so
70+
// we can compute correct relative paths on all platforms.
71+
const files = createEmailComponentList(fullPath, getFiles(fullPath));
7072

7173
if (!files.length) {
7274
return { files: null, path: emailPath };
@@ -78,8 +80,12 @@ export const emailList = ({
7880
const getEmailComponent = async (emailPath: string, file: string) => {
7981
const fileName = `${file}.svelte`;
8082
try {
83+
const normalizedEmailPath = emailPath.replace(/\\/g, '/').replace(/\/+$/, '');
84+
const normalizedFile = file.replace(/\\/g, '/').replace(/^\/+/, '');
85+
const importPath = `${normalizedEmailPath}/${normalizedFile}.svelte`;
86+
8187
// Import the email component dynamically
82-
return (await import(/* @vite-ignore */ path.join(emailPath, fileName))).default;
88+
return (await import(/* @vite-ignore */ importPath)).default;
8389
} catch (err) {
8490
throw new Error(
8591
`Failed to import email component '${fileName}'. Make sure the file exists and includes the <Head /> component.\nOriginal error: ${err}`
@@ -259,10 +265,10 @@ function createEmailComponentList(root: string, paths: string[]) {
259265

260266
paths.forEach((filePath) => {
261267
if (filePath.includes(`.svelte`)) {
262-
const fileName = filePath.substring(
263-
filePath.indexOf(root) + root.length + 1,
264-
filePath.indexOf('.svelte')
265-
);
268+
// Compute the path relative to the provided root and normalize separators
269+
// so the list displays consistently across platforms (use forward slashes)
270+
const relative = path.relative(root, filePath).replace(/\\/g, '/');
271+
const fileName = relative.replace(/\.svelte$/i, '');
266272
emailComponentList.push(fileName);
267273
}
268274
});
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/lib/emails/test-email.svelte renamed to src/lib/test/emails/test-email.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
import { Html, Head, Body, Text, Button, Container, Heading } from '../components/index.js';
2+
import { Html, Head, Body, Text, Button, Container, Heading } from '../../components/index.js';
33
import Template from './templates/title.svelte';
44
</script>
55

src/routes/preview/+page.server.ts

Lines changed: 25 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,28 @@
1-
import { sendEmail } from '$lib/preview/index.js';
2-
import { env } from '$env/dynamic/private';
3-
import type { PreviewData } from '$lib/preview/index.js';
4-
import type { RequestEvent } from '@sveltejs/kit';
5-
import { render } from 'svelte/server';
6-
import prettier from 'prettier/standalone';
7-
import parserHtml from 'prettier/parser-html';
8-
9-
// Import all email components at build time using import.meta.glob
10-
// This creates a map of all email components that can be accessed at runtime
11-
const emailModules = import.meta.glob('/src/lib/emails/**/*.svelte', { eager: true });
12-
13-
/**
14-
* Vercel-compatible email list function that uses Vite's import.meta.glob
15-
* to statically analyze email files at build time instead of runtime fs access
16-
*/
17-
function emailListVercel(): PreviewData {
18-
const files = Object.keys(emailModules)
19-
.map((path) => {
20-
// Extract filename without extension from the full path
21-
// e.g., '/src/lib/emails/apple-receipt.svelte' -> 'apple-receipt'
22-
const match = path.match(/\/src\/lib\/emails\/(.+)\.svelte$/);
23-
return match ? match[1] : null;
24-
})
25-
.filter((name): name is string => name !== null);
26-
27-
if (files.length === 0) {
28-
return { files: null, path: '/src/lib/emails' };
29-
}
30-
31-
return { files, path: '/src/lib/emails' };
32-
}
33-
34-
/**
35-
* Vercel-compatible createEmail action that uses pre-imported email components
36-
* instead of dynamic runtime imports
37-
*/
38-
const createEmailVercel = {
39-
'create-email': async (event: RequestEvent) => {
40-
try {
41-
const data = await event.request.formData();
42-
const file = data.get('file');
43-
const emailPath = data.get('path');
44-
45-
if (!file || !emailPath) {
46-
return {
47-
status: 400,
48-
body: { error: 'Missing file or path parameter' }
49-
};
50-
}
51-
52-
// Construct the full path to match the keys in emailModules
53-
const fullPath = `${emailPath}/${file}.svelte`;
54-
55-
// Get the component from the pre-imported modules
56-
const module = emailModules[fullPath] as { default: any } | undefined;
57-
58-
if (!module || !module.default) {
59-
throw new Error(
60-
`Failed to import email component '${file}' in '${emailPath}'. Make sure the file exists and includes the <Head /> component.`
61-
);
62-
}
63-
64-
const emailComponent = module.default;
65-
66-
// Render the component to HTML
67-
const { body } = render(emailComponent);
68-
69-
// Remove all HTML comments from the body before formatting
70-
const bodyWithoutComments = body.replace(/<!--[\s\S]*?-->/g, '');
71-
const formattedBody = await prettier.format(bodyWithoutComments, {
72-
parser: 'html',
73-
plugins: [parserHtml]
74-
});
75-
76-
return {
77-
body: formattedBody
78-
};
79-
} catch (error) {
80-
console.error('Error rendering email:', error);
81-
return {
82-
status: 500,
83-
error: {
84-
message: error instanceof Error ? error.message : 'Failed to render email'
85-
}
86-
};
87-
}
88-
}
89-
};
90-
91-
export function load() {
92-
const emails = emailListVercel();
93-
return { emails };
1+
//const emailModules = import.meta.glob('/src/lib/server/email/templates/**/*.svelte', { eager: true });
2+
3+
import { createEmail, emailList } from "$lib/preview/index.js";
4+
5+
/*function emailList(): PreviewData {
6+
const files = Object.keys(emailModules)
7+
.map((path) => {
8+
const match = path.match(/\/src\/lib\/server\/email\/templates\/(.+)\.svelte$/);
9+
return match ? match[1] : null;
10+
})
11+
.filter((name): name is string => name !== null);
12+
13+
if (files.length === 0) {
14+
return { files: null, path: '/src/lib/server/email/templates' };
15+
}
16+
return { files, path: '/src/lib/server/email/templates' };
17+
}*/
18+
19+
export async function load() {
20+
const emails = emailList({
21+
path: '/src/lib/test/emails',
22+
});
23+
return { emails };
9424
}
9525

9626
export const actions = {
97-
...createEmailVercel,
98-
...sendEmail({ resendApiKey: env.RESEND_API_KEY ?? 're_1234' })
99-
};
27+
...createEmail,
28+
};

0 commit comments

Comments
 (0)