@lupinum/nuxt-email 1.0.0-beta.1 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/README.md +22 -14
  3. package/dist/build.d.mts +13 -0
  4. package/dist/build.mjs +218 -0
  5. package/dist/module.json +1 -1
  6. package/dist/module.mjs +8 -134
  7. package/dist/runtime/render/index.d.ts +6 -0
  8. package/dist/runtime/render/index.js +3 -0
  9. package/dist/runtime/tailwind/css-tree.d.ts +10 -0
  10. package/dist/runtime/tailwind/css-tree.js +11 -0
  11. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.d.ts +1 -1
  12. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +1 -1
  13. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.d.ts +1 -1
  14. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +1 -1
  15. package/dist/runtime/tailwind/engine/css/get-custom-properties.d.ts +1 -1
  16. package/dist/runtime/tailwind/engine/css/get-custom-properties.js +1 -1
  17. package/dist/runtime/tailwind/engine/css/is-part-inlinable.d.ts +1 -1
  18. package/dist/runtime/tailwind/engine/css/is-part-inlinable.js +1 -1
  19. package/dist/runtime/tailwind/engine/css/is-rule-inlinable.d.ts +1 -1
  20. package/dist/runtime/tailwind/engine/css/is-rule-inlinable.js +1 -1
  21. package/dist/runtime/tailwind/engine/css/make-inline-styles-for.d.ts +1 -1
  22. package/dist/runtime/tailwind/engine/css/make-inline-styles-for.js +1 -1
  23. package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.d.ts +1 -1
  24. package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.js +1 -1
  25. package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.d.ts +1 -1
  26. package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.js +1 -1
  27. package/dist/runtime/tailwind/engine/css/sanitize-declarations.d.ts +1 -1
  28. package/dist/runtime/tailwind/engine/css/sanitize-declarations.js +1 -1
  29. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.d.ts +1 -1
  30. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +1 -1
  31. package/dist/runtime/tailwind/engine/css/split-mixed-rule.d.ts +1 -1
  32. package/dist/runtime/tailwind/engine/css/split-mixed-rule.js +1 -1
  33. package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.d.ts +1 -1
  34. package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.js +1 -1
  35. package/dist/runtime/tailwind/engine/index.js +1 -1
  36. package/dist/runtime/tailwind/engine/setup-tailwind.d.ts +1 -1
  37. package/dist/runtime/tailwind/engine/setup-tailwind.js +1 -1
  38. package/dist/runtime/tailwind/post-render.js +1 -1
  39. package/dist/shared/nuxt-email.B5ux0eBv.mjs +133 -0
  40. package/package.json +15 -6
package/CHANGELOG.md CHANGED
@@ -1,8 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.0.0-beta.2
4
+
5
+ ### Added
6
+
7
+ - Added `@lupinum/nuxt-email/build` to compile the existing typed email
8
+ registry for Node runtimes outside Nitro.
9
+ - Added `@lupinum/nuxt-email/render` so compiled templates reuse the same
10
+ deterministic renderer, metadata, plain-text generation, and email-safe
11
+ components as Nuxt applications.
12
+
13
+ ### Production boundaries
14
+
15
+ - Keep delivery, retries, idempotency, provider credentials, and audit state
16
+ application-owned. The package compiles and renders email; it does not send.
17
+ - Reject source directories outside the application root, refuse to replace
18
+ output not owned by Nuxt Email, and preserve the last valid registry when a
19
+ build fails.
20
+ - Exclude preview fixtures and source templates from the generated production
21
+ registry.
22
+
3
23
  ## v1.0.0-beta.1
4
24
 
5
- Status: certified beta candidate. Feature development is frozen until stable `1.0.0`; manual email-client QA and the external transactional beta remain pending.
25
+ Status: published on npm's `next` tag. Feature development is frozen until stable `1.0.0`. The three-client beta smoke test passed; the full eight-client checklist and external transactional beta remain pending.
6
26
 
7
27
  ### Added
8
28
 
package/README.md CHANGED
@@ -25,7 +25,7 @@
25
25
  </p>
26
26
 
27
27
  > [!WARNING]
28
- > Nuxt Email is in the `1.0.0-beta.1` contract freeze. Review the changelog before each upgrade. Manual inbox QA and the external beta are still pending. The unscoped `nuxt-email` package on npm is unrelated to this project.
28
+ > [`1.0.0-beta.2`](https://www.npmjs.com/package/@lupinum/nuxt-email/v/1.0.0-beta.2) is published on npm's `next` tag. The [three-client beta smoke test](https://github.com/lupinum-dev/nuxt-email/blob/main/docs/release/client-qa-checklist.md#100-beta1-smoke-test) passed against beta.1; the full eight-client checklist and an external transactional beta still block stable `1.0.0`. The unscoped `nuxt-email` package on npm is unrelated to this project.
29
29
 
30
30
  ## Why use Nuxt Email?
31
31
 
@@ -52,7 +52,7 @@ CI tests every supported Node major. The release consumer installs Nuxt `4.5.2`
52
52
  Install and register the module:
53
53
 
54
54
  ```bash
55
- pnpm add @lupinum/nuxt-email
55
+ pnpm add @lupinum/nuxt-email@next
56
56
  ```
57
57
 
58
58
  ```ts
@@ -69,19 +69,30 @@ Create `app/emails/welcome.vue`:
69
69
  <script setup lang="ts">
70
70
  import { defineEmail } from '@lupinum/nuxt-email/define-email'
71
71
 
72
- const props = defineProps<{ firstName: string; activationUrl: string }>()
72
+ const props = defineProps<{
73
+ firstName: string
74
+ activationUrl: string
75
+ }>()
73
76
 
74
- defineEmail({ subject: () => `Welcome, ${props.firstName}` })
77
+ defineEmail({
78
+ subject: () => `Welcome aboard, ${props.firstName}`,
79
+ })
75
80
  </script>
76
81
 
77
82
  <template>
78
83
  <ETailwind>
79
84
  <EHtml lang="en">
80
- <EBody class="bg-slate-100 p-6">
85
+ <EHead>
86
+ <title>Activate your account</title>
87
+ </EHead>
88
+ <EBody class="m-0 bg-slate-100 p-6">
81
89
  <EPreview>Your account is ready.</EPreview>
82
- <EContainer class="bg-white p-6">
83
- <EHeading>Welcome, {{ firstName }}</EHeading>
84
- <EButton :href="activationUrl">Activate account</EButton>
90
+ <EContainer class="rounded-lg bg-white p-6">
91
+ <EHeading class="m-0 text-2xl text-slate-900">Welcome, {{ firstName }}</EHeading>
92
+ <EText class="text-slate-600">Finish setting up your account.</EText>
93
+ <EButton class="rounded-md bg-blue-600 px-5 py-3 text-white" :href="activationUrl">
94
+ Activate account
95
+ </EButton>
85
96
  </EContainer>
86
97
  </EBody>
87
98
  </EHtml>
@@ -100,7 +111,7 @@ export default defineEventHandler(async () => {
100
111
  })
101
112
  ```
102
113
 
103
- The template name and props are generated from the Vue file. Invalid names and props fail during type checking.
114
+ The template name and props are generated from the Vue file. Invalid names and props fail during type checking. Use a sibling fixture to inspect it in `/__email`, then [pass the rendered result to your provider](https://nuxt-email.lupinum.com/docs/guides/sending-email).
104
115
 
105
116
  ## Discord
106
117
 
@@ -175,14 +186,11 @@ Complete the [manual client QA checklist](https://github.com/lupinum-dev/nuxt-em
175
186
 
176
187
  ## Package exports
177
188
 
178
- - `@lupinum/nuxt-email` provides the Nuxt module plus the `RenderedEmail` and `EmailComponentProps` types.
179
- - `@lupinum/nuxt-email/define-email` provides typed template metadata.
180
- - `@lupinum/nuxt-email/testing` provides standalone component rendering.
181
- - `@lupinum/nuxt-email/errors` provides supported runtime error classes.
189
+ The package has a module entry point plus focused build, production rendering, metadata, testing, and error subpaths. Use `@lupinum/nuxt-email/build` to compile a server registry and `@lupinum/nuxt-email/render` for compiled components outside Nitro. The [standalone guide](https://nuxt-email.lupinum.com/docs/guides/standalone-rendering) explains the build and runtime boundaries. The [canonical entry-point table](https://nuxt-email.lupinum.com/docs/reference/module#package-entry-points) lists every runtime and type-only export.
182
190
 
183
191
  ## Documentation
184
192
 
185
- Read the [Nuxt Email documentation](https://nuxt-email.lupinum.com/docs). It contains the [installation guide](https://nuxt-email.lupinum.com/docs/getting-started/installation), [component reference](https://nuxt-email.lupinum.com/docs/components), [preview guide](https://nuxt-email.lupinum.com/docs/guides/preview-workflow), [testing guide](https://nuxt-email.lupinum.com/docs/guides/testing-your-emails), and [sending guide](https://nuxt-email.lupinum.com/docs/guides/sending-email).
193
+ Read the [Nuxt Email documentation](https://nuxt-email.lupinum.com/docs). Start with the [installation guide](https://nuxt-email.lupinum.com/docs/getting-started/installation) and [component reference](https://nuxt-email.lupinum.com/docs/components). Use the guides for [preview](https://nuxt-email.lupinum.com/docs/guides/preview-workflow), [testing](https://nuxt-email.lupinum.com/docs/guides/testing-your-emails), and [sending](https://nuxt-email.lupinum.com/docs/guides/sending-email).
186
194
 
187
195
  See the [changelog](CHANGELOG.md) for released changes.
188
196
 
@@ -0,0 +1,13 @@
1
+ interface BuildEmailRegistryOptions {
2
+ /** Application/project root, without loading its Nuxt configuration. */
3
+ rootDir: string;
4
+ /** Template directory relative to rootDir. Defaults to app/emails. */
5
+ templatesDir?: string;
6
+ /** Dedicated directory inside rootDir. Must be absent or previously created by this helper. */
7
+ outDir: string;
8
+ }
9
+ /** Compile trusted application templates. Never call this from a request or action. */
10
+ declare function buildEmailRegistry(options: BuildEmailRegistryOptions): Promise<void>;
11
+
12
+ export { buildEmailRegistry };
13
+ export type { BuildEmailRegistryOptions };
package/dist/build.mjs ADDED
@@ -0,0 +1,218 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile, lstat, mkdir, mkdtemp, writeFile, rm, readdir, rename } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import { relative, dirname, resolve, isAbsolute, join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { build } from 'esbuild';
7
+ import ts from 'typescript';
8
+ import { d as discoverEmailTemplates, g as generateEmailRegistry, a as generateEmailTypes } from './shared/nuxt-email.B5ux0eBv.mjs';
9
+ import { createHash } from 'node:crypto';
10
+ import { parse, compileScript, compileTemplate } from 'vue/compiler-sfc';
11
+ import '@nuxt/kit';
12
+
13
+ function emailTemplatesPlugin(directory) {
14
+ return {
15
+ name: "nuxt-email-templates",
16
+ setup(build) {
17
+ build.onLoad({ filter: /\.vue$/ }, async ({ path }) => {
18
+ const { descriptor, errors } = parse(await readFile(path, "utf8"), { filename: path });
19
+ if (errors.length > 0) throw errors[0];
20
+ if (descriptor.styles.length || descriptor.customBlocks.length) {
21
+ throw new Error("Standalone email templates do not support style or custom blocks. Use ETailwind or inline styles.");
22
+ }
23
+ if ([descriptor.script, descriptor.scriptSetup, descriptor.template].some((block) => block?.src)) {
24
+ throw new Error("Standalone email templates do not support external SFC blocks. Import local components instead.");
25
+ }
26
+ if ([descriptor.script, descriptor.scriptSetup].some((block) => block?.lang && !["js", "ts"].includes(block.lang)) || descriptor.template?.lang && descriptor.template.lang !== "html") {
27
+ throw new Error("Standalone email templates support JavaScript/TypeScript scripts and HTML templates only.");
28
+ }
29
+ const id = createHash("sha256").update(relative(directory, path).replaceAll("\\", "/")).digest("hex").slice(0, 8);
30
+ const script = descriptor.script || descriptor.scriptSetup ? compileScript(descriptor, { id, genDefaultAs: "__emailComponent", inlineTemplate: false, isProd: true }) : void 0;
31
+ const template = descriptor.template ? compileTemplate({
32
+ source: descriptor.template.content,
33
+ filename: path,
34
+ id,
35
+ isProd: true,
36
+ compilerOptions: { bindingMetadata: script?.bindings },
37
+ transformAssetUrls: false
38
+ }) : void 0;
39
+ if (template?.errors.length) throw template.errors[0];
40
+ return {
41
+ contents: [
42
+ script?.content ?? "const __emailComponent = {}",
43
+ template?.code ?? "",
44
+ template ? "__emailComponent.render = render" : "",
45
+ "export default __emailComponent"
46
+ ].join("\n"),
47
+ loader: "ts",
48
+ resolveDir: dirname(path)
49
+ };
50
+ });
51
+ }
52
+ };
53
+ }
54
+
55
+ const execute = promisify(execFile);
56
+ const require$1 = createRequire(import.meta.url);
57
+ const renderImport = "@lupinum/nuxt-email/render";
58
+ const ownershipMarker = ".nuxt-email-generated";
59
+ const ownershipContent = "Generated by @lupinum/nuxt-email/build. Do not edit.\n";
60
+ function contains(parent, child) {
61
+ const path = relative(parent, child);
62
+ return path === "" || !path.startsWith(`..`) && !isAbsolute(path);
63
+ }
64
+ async function replaceOutputDirectory(staging, outDir, replaceOutput) {
65
+ const previousRoot = await mkdtemp(join(dirname(outDir), ".nuxt-email-previous-"));
66
+ const previous = join(previousRoot, "output");
67
+ let removePreviousRoot = true;
68
+ try {
69
+ if (replaceOutput) await rename(outDir, previous);
70
+ try {
71
+ await rename(staging, outDir);
72
+ } catch (error) {
73
+ if (replaceOutput) {
74
+ try {
75
+ await rename(previous, outDir);
76
+ } catch (restoreError) {
77
+ removePreviousRoot = false;
78
+ throw new AggregateError(
79
+ [error, restoreError],
80
+ `Failed to replace generated email output and restore it. The prior output remains at ${previous}.`,
81
+ { cause: restoreError }
82
+ );
83
+ }
84
+ }
85
+ throw error;
86
+ }
87
+ } finally {
88
+ if (removePreviousRoot) await rm(previousRoot, { recursive: true, force: true });
89
+ }
90
+ }
91
+ async function normalizeDeclarationImports(directory) {
92
+ for (const file of await readdir(directory, { recursive: true })) {
93
+ if (!/\.d\.[cm]?ts$/.test(file)) continue;
94
+ const path = join(directory, file);
95
+ const source = ts.createSourceFile(path, await readFile(path, "utf8"), ts.ScriptTarget.Latest, true);
96
+ const transformed = ts.transform(source, [(context) => {
97
+ const visit = (node) => {
98
+ if (ts.isStringLiteral(node) && node.text.startsWith(".")) {
99
+ const parent = node.parent;
100
+ const isImport = ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent) || ts.isLiteralTypeNode(parent) && ts.isImportTypeNode(parent.parent);
101
+ if (isImport && !node.text.endsWith(".json")) {
102
+ const module = ts.resolveModuleName(node.text, path, { moduleResolution: ts.ModuleResolutionKind.Bundler }, ts.sys).resolvedModule;
103
+ if (!module) throw new Error(`Cannot resolve emitted declaration import ${JSON.stringify(node.text)} from ${path}. Keep local imports inside the email directory.`);
104
+ const target = relative(dirname(path), module.resolvedFileName).replaceAll("\\", "/").replace(/\.d\.([cm]?)ts$/, ".$1js");
105
+ return ts.factory.createStringLiteral(target.startsWith(".") ? target : `./${target}`);
106
+ }
107
+ }
108
+ return ts.visitEachChild(node, visit, context);
109
+ };
110
+ return (node) => ts.visitNode(node, visit);
111
+ }]);
112
+ try {
113
+ await writeFile(path, ts.createPrinter().printFile(transformed.transformed[0]));
114
+ } finally {
115
+ transformed.dispose();
116
+ }
117
+ }
118
+ }
119
+ async function buildEmailRegistry(options) {
120
+ if (Object.keys(options).some((key) => key !== "rootDir" && key !== "outDir" && key !== "templatesDir")) {
121
+ throw new TypeError("buildEmailRegistry accepts only rootDir, outDir and templatesDir.");
122
+ }
123
+ const rootDir = resolve(options.rootDir);
124
+ const templatesDir = options.templatesDir ?? "app/emails";
125
+ if (isAbsolute(templatesDir)) throw new TypeError("templatesDir must be relative to rootDir.");
126
+ const directory = resolve(rootDir, templatesDir);
127
+ if (!contains(rootDir, directory)) throw new TypeError("templatesDir must remain inside rootDir.");
128
+ const outDir = resolve(rootDir, options.outDir);
129
+ if (!contains(rootDir, outDir)) throw new TypeError("outDir must remain inside rootDir so the generated module can resolve project dependencies.");
130
+ if (contains(directory, outDir) || contains(outDir, directory)) {
131
+ throw new TypeError("Generated output must be outside and must not contain the email directory.");
132
+ }
133
+ let replaceOutput = false;
134
+ try {
135
+ const output = await lstat(outDir);
136
+ if (!output.isDirectory() || output.isSymbolicLink()) throw new TypeError("outDir must be a generated directory, not a file or symlink.");
137
+ const marker = await readFile(join(outDir, ownershipMarker), "utf8").catch(() => "");
138
+ if (marker !== ownershipContent) throw new TypeError("Refusing to replace an outDir not owned by this build.");
139
+ replaceOutput = true;
140
+ } catch (error) {
141
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
142
+ }
143
+ const templates = await discoverEmailTemplates(directory);
144
+ if (templates.length === 0) throw new TypeError("The email directory contains no templates.");
145
+ await mkdir(dirname(outDir), { recursive: true });
146
+ const staging = await mkdtemp(join(dirname(outDir), ".nuxt-email-build-"));
147
+ try {
148
+ const declarations = join(staging, "types");
149
+ const globals = join(staging, "components.d.ts");
150
+ await writeFile(globals, `import type { EmailComponents } from '${renderImport}'
151
+ declare module 'vue' { interface GlobalComponents extends EmailComponents {} }
152
+ export {}
153
+ `);
154
+ const config = join(staging, "tsconfig.json");
155
+ await writeFile(config, JSON.stringify({
156
+ compilerOptions: {
157
+ target: "ES2022",
158
+ module: "ESNext",
159
+ moduleResolution: "Bundler",
160
+ strict: true,
161
+ skipLibCheck: true,
162
+ declaration: true,
163
+ emitDeclarationOnly: true,
164
+ noEmitOnError: true,
165
+ rootDir: directory,
166
+ declarationDir: declarations,
167
+ types: [],
168
+ lib: ["ES2022", "DOM", "DOM.Iterable"]
169
+ },
170
+ vueCompilerOptions: { strictTemplates: true },
171
+ files: [globals, ...templates.map((template) => template.sourcePath)]
172
+ }));
173
+ try {
174
+ await execute(process.execPath, [require$1.resolve("vue-tsc/bin/vue-tsc.js"), "-p", config], {
175
+ cwd: directory,
176
+ maxBuffer: 4 * 1024 * 1024
177
+ });
178
+ } catch (error) {
179
+ const diagnostics = error instanceof Error && "stdout" in error && typeof error.stdout === "string" ? error.stdout.trim() : "";
180
+ throw new Error(`Email declaration build failed${diagnostics ? `:
181
+ ${diagnostics}` : "."}`, { cause: error });
182
+ }
183
+ await normalizeDeclarationImports(declarations);
184
+ const registry = generateEmailRegistry(templates, {
185
+ emailRenderError: renderImport,
186
+ renderEmailComponent: renderImport,
187
+ templateRegistryErrors: renderImport
188
+ });
189
+ await build({
190
+ absWorkingDir: rootDir,
191
+ tsconfigRaw: { compilerOptions: { target: "ES2022", module: "ESNext" } },
192
+ stdin: { contents: registry, resolveDir: directory, sourcefile: "email-registry.js" },
193
+ outfile: join(staging, "index.mjs"),
194
+ bundle: true,
195
+ format: "esm",
196
+ platform: "node",
197
+ target: "node22",
198
+ packages: "external",
199
+ logLevel: "silent",
200
+ plugins: [emailTemplatesPlugin(directory)]
201
+ });
202
+ await writeFile(join(staging, "index.d.mts"), generateEmailTypes(
203
+ templates.map((template) => ({
204
+ name: template.name,
205
+ sourcePath: `./types/${template.name}.vue.js`
206
+ })),
207
+ { renderedEmail: renderImport }
208
+ ));
209
+ await writeFile(join(staging, ownershipMarker), ownershipContent);
210
+ await rm(config);
211
+ await rm(globals);
212
+ await replaceOutputDirectory(staging, outDir, replaceOutput);
213
+ } finally {
214
+ await rm(staging, { recursive: true, force: true });
215
+ }
216
+ }
217
+
218
+ export { buildEmailRegistry };
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.5.1 <5"
6
6
  },
7
- "version": "1.0.0-beta.1",
7
+ "version": "1.0.0-beta.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,8 +1,9 @@
1
- import { relative, isAbsolute, sep, resolve } from 'node:path';
2
- import { resolveFiles, defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, addServerImports, addComponent, addServerHandler, useNitro, updateTemplates } from '@nuxt/kit';
1
+ import { resolve, isAbsolute, relative } from 'node:path';
2
+ import { defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, addServerImports, addComponent, addServerHandler, useNitro, updateTemplates } from '@nuxt/kit';
3
3
  import vueRollup from 'unplugin-vue/rollup';
4
4
  import { stat } from 'node:fs/promises';
5
5
  import { EMAIL_COMPONENT_NAMES } from '../dist/runtime/components/email-component-names.js';
6
+ import { g as generateEmailRegistry, a as generateEmailTypes, d as discoverEmailTemplates } from './shared/nuxt-email.B5ux0eBv.mjs';
6
7
 
7
8
  function previewFixturePath(sourcePath) {
8
9
  return `${sourcePath.slice(0, -".vue".length)}.fixtures.ts`;
@@ -26,135 +27,6 @@ async function attachPreviewFixtures(templates) {
26
27
  }));
27
28
  }
28
29
 
29
- class DuplicateEmailTemplateError extends Error {
30
- templateName;
31
- sourcePaths;
32
- constructor(templateName, sourcePaths) {
33
- const sortedSourcePaths = [...sourcePaths].sort();
34
- super(`Duplicate email template name "${templateName}" from: ${sortedSourcePaths.join(", ")}`);
35
- this.name = "DuplicateEmailTemplateError";
36
- this.templateName = templateName;
37
- this.sourcePaths = sortedSourcePaths;
38
- }
39
- }
40
- class EmailTemplateDiscoveryError extends Error {
41
- sourcePath;
42
- constructor(sourcePath, cause) {
43
- super(`Failed to discover email templates under ${sourcePath}`, { cause });
44
- this.name = "EmailTemplateDiscoveryError";
45
- this.sourcePath = sourcePath;
46
- }
47
- }
48
-
49
- function compareCodePoints(left, right) {
50
- return left < right ? -1 : left > right ? 1 : 0;
51
- }
52
- function normalizeSeparators(value) {
53
- return value.split(sep).join("/").replaceAll("\\", "/");
54
- }
55
- function templatesFromSourcePaths(emailDirectory, sourcePaths) {
56
- const templates = sourcePaths.map((sourcePath) => {
57
- const relativePath = normalizeSeparators(relative(emailDirectory, sourcePath));
58
- if (relativePath.startsWith("../") || relativePath === ".." || isAbsolute(relativePath)) {
59
- throw new EmailTemplateDiscoveryError(sourcePath, new TypeError("Template path is outside app/emails"));
60
- }
61
- return {
62
- name: relativePath.slice(0, -".vue".length),
63
- sourcePath: normalizeSeparators(sourcePath)
64
- };
65
- }).sort((left, right) => {
66
- return compareCodePoints(left.name, right.name) || compareCodePoints(left.sourcePath, right.sourcePath);
67
- });
68
- const pathsByName = /* @__PURE__ */ new Map();
69
- for (const template of templates) {
70
- const paths = pathsByName.get(template.name) ?? [];
71
- paths.push(template.sourcePath);
72
- pathsByName.set(template.name, paths);
73
- }
74
- const duplicate = [...pathsByName.entries()].filter(([, paths]) => paths.length > 1).sort(([left], [right]) => compareCodePoints(left, right))[0];
75
- if (duplicate) {
76
- throw new DuplicateEmailTemplateError(duplicate[0], duplicate[1]);
77
- }
78
- return templates;
79
- }
80
- async function discoverEmailTemplates(emailDirectory) {
81
- let sourcePaths;
82
- try {
83
- sourcePaths = await resolveFiles(emailDirectory, "**/*.vue", {
84
- followSymbolicLinks: false,
85
- ignore: ["components/**"]
86
- });
87
- } catch (error) {
88
- throw new EmailTemplateDiscoveryError(emailDirectory, error);
89
- }
90
- return templatesFromSourcePaths(emailDirectory, sourcePaths);
91
- }
92
-
93
- function importPath(path) {
94
- return JSON.stringify(path.replaceAll("\\", "/"));
95
- }
96
- function generateEmailRegistry(templates, runtimePaths) {
97
- const entries = templates.map((template) => {
98
- const fixture = template.fixturePath ? `
99
- fixture: () => import(${importPath(template.fixturePath)}),` : "";
100
- return ` [${JSON.stringify(template.name)}]: {
101
- component: () => import(${importPath(template.sourcePath)}),${fixture}
102
- },`;
103
- });
104
- return `import { EmailRenderError } from ${importPath(runtimePaths.emailRenderError)}
105
- import { renderEmailComponent } from ${importPath(runtimePaths.renderEmailComponent)}
106
- import { UnknownEmailTemplateError } from ${importPath(runtimePaths.templateRegistryErrors)}
107
-
108
- export const emailTemplates = Object.freeze({
109
- ${entries.join("\n")}
110
- })
111
-
112
- export async function renderEmail(name, props) {
113
- const loader = typeof name === 'string' && Object.hasOwn(emailTemplates, name)
114
- ? emailTemplates[name]
115
- : undefined
116
- if (!loader) {
117
- throw new UnknownEmailTemplateError(String(name), Object.keys(emailTemplates))
118
- }
119
-
120
- try {
121
- const templateModule = await loader.component()
122
- return await renderEmailComponent(templateModule.default, props)
123
- }
124
- catch (error) {
125
- throw new EmailRenderError(name, error instanceof EmailRenderError ? error.cause : error)
126
- }
127
- }
128
- `;
129
- }
130
- function generateEmailTypes(templates, typePaths) {
131
- const entries = templates.map((template) => {
132
- const fixture = template.fixturePath ? `
133
- fixture: () => Promise<typeof import(${importPath(template.fixturePath)})>` : "";
134
- return ` ${JSON.stringify(template.name)}: {
135
- component: () => Promise<typeof import(${importPath(template.sourcePath)})>${fixture}
136
- }`;
137
- });
138
- return `export const emailTemplates: Readonly<{
139
- ${entries.join("\n")}
140
- }>
141
-
142
- export type EmailTemplateName = keyof typeof emailTemplates
143
- type _EmailProps<Component> = keyof import(${importPath(typePaths.renderedEmail)}).EmailComponentProps<Component> extends never
144
- ? Record<string, never>
145
- : import(${importPath(typePaths.renderedEmail)}).EmailComponentProps<Component>
146
-
147
- export type EmailTemplateProps = {
148
- [Name in EmailTemplateName]: _EmailProps<Awaited<ReturnType<(typeof emailTemplates)[Name]['component']>>['default']>
149
- }
150
-
151
- export function renderEmail<Name extends EmailTemplateName>(
152
- name: Name,
153
- props: NoInfer<EmailTemplateProps[Name]>,
154
- ): Promise<import(${importPath(typePaths.renderedEmail)}).RenderedEmail>
155
- `;
156
- }
157
-
158
30
  function configureEmailTypeProject(tsConfig, emailDirectory, registryId, registryPath) {
159
31
  const normalizedDirectory = emailDirectory.replaceAll("\\", "/");
160
32
  const include = tsConfig.include ??= [];
@@ -199,10 +71,12 @@ const module$1 = defineNuxtModule({
199
71
  const resolver = createResolver(import.meta.url);
200
72
  const defineEmailPublicPath = await resolver.resolvePath("./runtime/define-email");
201
73
  const errorsPublicPath = await resolver.resolvePath("./runtime/errors");
202
- let renderEmailComponentPath = await resolver.resolvePath("./runtime/testing");
74
+ let renderEmailComponentPath = await resolver.resolvePath("./runtime/render/index");
75
+ let testingRendererPath = await resolver.resolvePath("./runtime/testing");
203
76
  if (options.codeBlock !== void 0) {
204
77
  const { setupCodeBlock } = await import('./chunks/setup.mjs');
205
78
  renderEmailComponentPath = setupCodeBlock(options.codeBlock, resolver);
79
+ testingRendererPath = renderEmailComponentPath;
206
80
  }
207
81
  const emailDirectory = resolve(nuxt.options.srcDir, "emails");
208
82
  const loadTemplates = async () => {
@@ -312,7 +186,7 @@ const module$1 = defineNuxtModule({
312
186
  const nuxtOptions = nuxt.options;
313
187
  const nitro = nuxtOptions.nitro ??= {};
314
188
  const alias = nitro.alias ??= {};
315
- alias["#nuxt-email/testing"] = renderEmailComponentPath;
189
+ alias["#nuxt-email/testing"] = testingRendererPath;
316
190
  alias["@lupinum/nuxt-email/define-email"] = defineEmailPublicPath;
317
191
  alias["@lupinum/nuxt-email/errors"] = errorsPublicPath;
318
192
  const externals = nitro.externals ??= {};
@@ -325,7 +199,7 @@ const module$1 = defineNuxtModule({
325
199
  const typescript = nitro.typescript ??= {};
326
200
  const tsConfig = typescript.tsConfig ??= {};
327
201
  const paths = configureEmailTypeProject(tsConfig, emailDirectory, registryId, typeTemplate.dst);
328
- paths["#nuxt-email/testing"] = [renderEmailComponentPath];
202
+ paths["#nuxt-email/testing"] = [testingRendererPath];
329
203
  nitro.rollupConfig ??= {};
330
204
  const existingPlugins = nitro.rollupConfig.plugins;
331
205
  nitro.rollupConfig.plugins = existingPlugins ? [existingPlugins, vueRollup()] : [vueRollup()];
@@ -0,0 +1,6 @@
1
+ /** Server-only production rendering of already compiled Vue components. */
2
+ export { renderEmailComponent } from './render-email-component.js';
3
+ export { EmailRenderError } from './errors.js';
4
+ export { UnknownEmailTemplateError } from '../template-registry/errors.js';
5
+ export type { EmailComponentProps, RenderedEmail } from './types.js';
6
+ export type EmailComponents = typeof import('../components/email-component-registry').emailComponentRegistry;
@@ -0,0 +1,3 @@
1
+ export { renderEmailComponent } from "./render-email-component.js";
2
+ export { EmailRenderError } from "./errors.js";
3
+ export { UnknownEmailTemplateError } from "../template-registry/errors.js";
@@ -0,0 +1,10 @@
1
+ import type * as CssTree from 'css-tree';
2
+ export declare const parse: typeof CssTree.parse;
3
+ export declare const generate: typeof CssTree.generate;
4
+ export declare const walk: typeof CssTree.walk;
5
+ export declare const find: typeof CssTree.find;
6
+ export declare const clone: typeof CssTree.clone;
7
+ export declare const List: typeof CssTree.List;
8
+ export type List<T> = CssTree.List<T>;
9
+ export declare const string: typeof CssTree.string;
10
+ export type * from 'css-tree';
@@ -0,0 +1,11 @@
1
+ import parser from "css-tree/parser";
2
+ import generator from "css-tree/generator";
3
+ import walker from "css-tree/walker";
4
+ import { clone as cloneNode, List as NodeList, string as cssString } from "css-tree/utils";
5
+ export const parse = parser;
6
+ export const generate = generator;
7
+ export const walk = walker;
8
+ export const find = walker.find;
9
+ export const clone = cloneNode;
10
+ export const List = NodeList;
11
+ export const string = cssString;
@@ -3,7 +3,7 @@
3
3
  * can parse. The transform is deliberately strict: leaving one nested rule in
4
4
  * the output is worse than failing the render with an actionable error.
5
5
  */
6
- import { type StyleSheet } from 'css-tree';
6
+ import { type StyleSheet } from '../../css-tree.js';
7
7
  /**
8
8
  * css-tree 3.x parses media range syntax, but the currently published type
9
9
  * package does not expose the corresponding nodes yet.
@@ -4,7 +4,7 @@ import {
4
4
  List,
5
5
  parse,
6
6
  walk
7
- } from "css-tree";
7
+ } from "../../css-tree.js";
8
8
  export function downlevelForEmailClients(styleSheet) {
9
9
  styleSheet.children = flattenStyleSheet(styleSheet);
10
10
  downlevelRangeMediaQueries(styleSheet);
@@ -1,4 +1,4 @@
1
- import { type CssNode, type Rule } from 'css-tree';
1
+ import { type CssNode, type Rule } from '../../css-tree.js';
2
2
  export interface ExtractedRules {
3
3
  inlinable: Map<string, Rule[]>;
4
4
  nonInlinable: Map<string, Rule[]>;
@@ -1,4 +1,4 @@
1
- import { string, walk } from "css-tree";
1
+ import { string, walk } from "../../css-tree.js";
2
2
  import { isRuleInlinable } from "./is-rule-inlinable.js";
3
3
  import { splitMixedRule } from "./split-mixed-rule.js";
4
4
  export function extractRulesPerClass(root, classes) {
@@ -1,4 +1,4 @@
1
- import { type CssNode, type Declaration } from 'css-tree';
1
+ import { type CssNode, type Declaration } from '../../css-tree.js';
2
2
  export interface CustomProperty {
3
3
  syntax?: Declaration;
4
4
  inherits?: Declaration;
@@ -1,4 +1,4 @@
1
- import { generate, walk } from "css-tree";
1
+ import { generate, walk } from "../../css-tree.js";
2
2
  export function getCustomProperties(node) {
3
3
  const customProperties = /* @__PURE__ */ new Map();
4
4
  walk(node, {
@@ -1,2 +1,2 @@
1
- import { type CssNode } from 'css-tree';
1
+ import { type CssNode } from '../../css-tree.js';
2
2
  export declare function isPartInlinable(part: CssNode): boolean;
@@ -1,4 +1,4 @@
1
- import { find } from "css-tree";
1
+ import { find } from "../../css-tree.js";
2
2
  export function isPartInlinable(part) {
3
3
  const hasAtRuleInside = find(part, (node) => node.type === "Atrule") !== null;
4
4
  const hasPseudoSelector = find(
@@ -1,2 +1,2 @@
1
- import { type Rule } from 'css-tree';
1
+ import { type Rule } from '../../css-tree.js';
2
2
  export declare function isRuleInlinable(rule: Rule): boolean;
@@ -1,4 +1,4 @@
1
- import { find } from "css-tree";
1
+ import { find } from "../../css-tree.js";
2
2
  export function isRuleInlinable(rule) {
3
3
  const hasAtRuleInside = find(rule, (node) => node.type === "Atrule") !== null;
4
4
  const hasPseudoSelector = find(
@@ -1,4 +1,4 @@
1
- import { type CssNode } from 'css-tree';
1
+ import { type CssNode } from '../../css-tree.js';
2
2
  import type { CustomProperties } from './get-custom-properties.js';
3
3
  /**
4
4
  * Adapted from React Email's `make-inline-styles-for.ts`.
@@ -1,4 +1,4 @@
1
- import { generate, walk } from "css-tree";
1
+ import { generate, walk } from "../../css-tree.js";
2
2
  import { getCssProperty } from "./get-css-property.js";
3
3
  import { stripEmptyTailwindVars } from "./strip-empty-tailwind-vars.js";
4
4
  import { unwrapValue } from "./unwrap-value.js";
@@ -1,4 +1,4 @@
1
- import { type CssNode, type Declaration } from 'css-tree';
1
+ import { type CssNode, type Declaration } from '../../css-tree.js';
2
2
  export interface VariableDefinition {
3
3
  declaration: Declaration;
4
4
  path: CssNode[];
@@ -2,7 +2,7 @@ import {
2
2
  generate,
3
3
  parse,
4
4
  walk
5
- } from "css-tree";
5
+ } from "../../css-tree.js";
6
6
  function doSelectorsIntersect(first, second) {
7
7
  const firstStringified = generate(first);
8
8
  const secondStringified = generate(second);
@@ -1,4 +1,4 @@
1
- import { type CssNode } from 'css-tree';
1
+ import { type CssNode } from '../../css-tree.js';
2
2
  /**
3
3
  * Intentionally only resolves `*` and `/` operations without dealing with parenthesis, because this is the only thing required to run Tailwind v4
4
4
  */
@@ -1,4 +1,4 @@
1
- import { walk } from "css-tree";
1
+ import { walk } from "../../css-tree.js";
2
2
  export function resolveCalcExpressions(node) {
3
3
  walk(node, {
4
4
  visit: "Function",
@@ -1,4 +1,4 @@
1
- import { type CssNode } from 'css-tree';
1
+ import { type CssNode } from '../../css-tree.js';
2
2
  /**
3
3
  * Meant to do all the things necessary, in a per-declaration basis, to have the best email client
4
4
  * support possible.
@@ -3,7 +3,7 @@ import {
3
3
  List,
4
4
  parse,
5
5
  walk
6
- } from "css-tree";
6
+ } from "../../css-tree.js";
7
7
  function rgbNode(r, g, b, alpha) {
8
8
  const children = new List();
9
9
  children.appendData({
@@ -1,4 +1,4 @@
1
- import { type CssNode } from 'css-tree';
1
+ import { type CssNode } from '../../css-tree.js';
2
2
  /**
3
3
  * This function goes through a few steps to ensure the best email client support and
4
4
  * to ensure that media queries and pseudo classes are applied correctly alongside
@@ -1,4 +1,4 @@
1
- import { string, walk } from "css-tree";
1
+ import { string, walk } from "../../css-tree.js";
2
2
  import { sanitizeClassName } from "../compatibility/sanitize-class-name.js";
3
3
  import { isRuleInlinable } from "./is-rule-inlinable.js";
4
4
  import { stripEmptyTailwindVars } from "./strip-empty-tailwind-vars.js";
@@ -1,4 +1,4 @@
1
- import { type Rule } from 'css-tree';
1
+ import { type Rule } from '../../css-tree.js';
2
2
  /**
3
3
  * Split a rule into inlinable and non-inlinable parts. Caller must only pass rules
4
4
  * for which isRuleInlinable(rule) is false. Returns clones so the original stylesheet is never mutated.
@@ -1,4 +1,4 @@
1
- import { clone, find, List } from "css-tree";
1
+ import { clone, find, List } from "../../css-tree.js";
2
2
  import { isPartInlinable } from "./is-part-inlinable.js";
3
3
  export function splitMixedRule(rule) {
4
4
  const selectorHasPseudo = rule.prelude !== null && find(
@@ -1,4 +1,4 @@
1
- import { type CssNode } from 'css-tree';
1
+ import { type CssNode } from '../../css-tree.js';
2
2
  /**
3
3
  * Tailwind v4 emits variant-stacking idioms like
4
4
  * font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) tabular-nums var(--tw-numeric-fraction,)
@@ -1,4 +1,4 @@
1
- import { generate, walk } from "css-tree";
1
+ import { generate, walk } from "../../css-tree.js";
2
2
  export function stripEmptyTailwindVars(node) {
3
3
  walk(node, {
4
4
  visit: "Function",
@@ -3,7 +3,7 @@ import {
3
3
  generate,
4
4
  List,
5
5
  walk
6
- } from "css-tree";
6
+ } from "../css-tree.js";
7
7
  import { downlevelForEmailClients } from "./css/downlevel-for-email-clients.js";
8
8
  import { extractRulesPerClass } from "./css/extract-rules-per-class.js";
9
9
  import { getCustomProperties } from "./css/get-custom-properties.js";
@@ -1,4 +1,4 @@
1
- import { type StyleSheet } from 'css-tree';
1
+ import { type StyleSheet } from '../css-tree.js';
2
2
  import type { TailwindConfig } from './types.js';
3
3
  export type TailwindSetup = Awaited<ReturnType<typeof setupTailwind>>;
4
4
  interface CSSConfigs {
@@ -1,4 +1,4 @@
1
- import { parse } from "css-tree";
1
+ import { parse } from "../css-tree.js";
2
2
  import { compile } from "tailwindcss";
3
3
  import {
4
4
  indexCss,
@@ -1,4 +1,4 @@
1
- import { generate, parse, walk } from "css-tree";
1
+ import { generate, parse, walk } from "./css-tree.js";
2
2
  import { Parser } from "htmlparser2";
3
3
  import { classTokens, mergeInlinableStyle, residualClasses } from "./inline-utils.js";
4
4
  import { TailwindMissingHeadError } from "./errors.js";
@@ -0,0 +1,133 @@
1
+ import { relative, isAbsolute, sep } from 'node:path';
2
+ import { resolveFiles } from '@nuxt/kit';
3
+
4
+ class DuplicateEmailTemplateError extends Error {
5
+ templateName;
6
+ sourcePaths;
7
+ constructor(templateName, sourcePaths) {
8
+ const sortedSourcePaths = [...sourcePaths].sort();
9
+ super(`Duplicate email template name "${templateName}" from: ${sortedSourcePaths.join(", ")}`);
10
+ this.name = "DuplicateEmailTemplateError";
11
+ this.templateName = templateName;
12
+ this.sourcePaths = sortedSourcePaths;
13
+ }
14
+ }
15
+ class EmailTemplateDiscoveryError extends Error {
16
+ sourcePath;
17
+ constructor(sourcePath, cause) {
18
+ super(`Failed to discover email templates under ${sourcePath}`, { cause });
19
+ this.name = "EmailTemplateDiscoveryError";
20
+ this.sourcePath = sourcePath;
21
+ }
22
+ }
23
+
24
+ function compareCodePoints(left, right) {
25
+ return left < right ? -1 : left > right ? 1 : 0;
26
+ }
27
+ function normalizeSeparators(value) {
28
+ return value.split(sep).join("/").replaceAll("\\", "/");
29
+ }
30
+ function templatesFromSourcePaths(emailDirectory, sourcePaths) {
31
+ const templates = sourcePaths.map((sourcePath) => {
32
+ const relativePath = normalizeSeparators(relative(emailDirectory, sourcePath));
33
+ if (relativePath.startsWith("../") || relativePath === ".." || isAbsolute(relativePath)) {
34
+ throw new EmailTemplateDiscoveryError(sourcePath, new TypeError("Template path is outside app/emails"));
35
+ }
36
+ return {
37
+ name: relativePath.slice(0, -".vue".length),
38
+ sourcePath: normalizeSeparators(sourcePath)
39
+ };
40
+ }).sort((left, right) => {
41
+ return compareCodePoints(left.name, right.name) || compareCodePoints(left.sourcePath, right.sourcePath);
42
+ });
43
+ const pathsByName = /* @__PURE__ */ new Map();
44
+ for (const template of templates) {
45
+ const paths = pathsByName.get(template.name) ?? [];
46
+ paths.push(template.sourcePath);
47
+ pathsByName.set(template.name, paths);
48
+ }
49
+ const duplicate = [...pathsByName.entries()].filter(([, paths]) => paths.length > 1).sort(([left], [right]) => compareCodePoints(left, right))[0];
50
+ if (duplicate) {
51
+ throw new DuplicateEmailTemplateError(duplicate[0], duplicate[1]);
52
+ }
53
+ return templates;
54
+ }
55
+ async function discoverEmailTemplates(emailDirectory) {
56
+ let sourcePaths;
57
+ try {
58
+ sourcePaths = await resolveFiles(emailDirectory, "**/*.vue", {
59
+ followSymbolicLinks: false,
60
+ ignore: ["components/**"]
61
+ });
62
+ } catch (error) {
63
+ throw new EmailTemplateDiscoveryError(emailDirectory, error);
64
+ }
65
+ return templatesFromSourcePaths(emailDirectory, sourcePaths);
66
+ }
67
+
68
+ function importPath(path) {
69
+ return JSON.stringify(path.replaceAll("\\", "/"));
70
+ }
71
+ function generateEmailRegistry(templates, runtimePaths) {
72
+ const entries = templates.map((template) => {
73
+ const fixture = template.fixturePath ? `
74
+ fixture: () => import(${importPath(template.fixturePath)}),` : "";
75
+ return ` [${JSON.stringify(template.name)}]: {
76
+ component: () => import(${importPath(template.sourcePath)}),${fixture}
77
+ },`;
78
+ });
79
+ return `import { EmailRenderError } from ${importPath(runtimePaths.emailRenderError)}
80
+ import { renderEmailComponent } from ${importPath(runtimePaths.renderEmailComponent)}
81
+ import { UnknownEmailTemplateError } from ${importPath(runtimePaths.templateRegistryErrors)}
82
+
83
+ export const emailTemplates = Object.freeze({
84
+ ${entries.join("\n")}
85
+ })
86
+
87
+ export async function renderEmail(name, props) {
88
+ const loader = typeof name === 'string' && Object.hasOwn(emailTemplates, name)
89
+ ? emailTemplates[name]
90
+ : undefined
91
+ if (!loader) {
92
+ throw new UnknownEmailTemplateError(String(name), Object.keys(emailTemplates))
93
+ }
94
+
95
+ try {
96
+ const templateModule = await loader.component()
97
+ return await renderEmailComponent(templateModule.default, props)
98
+ }
99
+ catch (error) {
100
+ throw new EmailRenderError(name, error instanceof EmailRenderError ? error.cause : error)
101
+ }
102
+ }
103
+ `;
104
+ }
105
+ function generateEmailTypes(templates, typePaths) {
106
+ const entries = templates.map((template) => {
107
+ const fixture = template.fixturePath ? `
108
+ fixture: () => Promise<typeof import(${importPath(template.fixturePath)})>` : "";
109
+ return ` ${JSON.stringify(template.name)}: {
110
+ component: () => Promise<typeof import(${importPath(template.sourcePath)})>${fixture}
111
+ }`;
112
+ });
113
+ return `export const emailTemplates: Readonly<{
114
+ ${entries.join("\n")}
115
+ }>
116
+
117
+ export type EmailTemplateName = keyof typeof emailTemplates
118
+ type _EmailProps<Component> = keyof import(${importPath(typePaths.renderedEmail)}).EmailComponentProps<Component> extends never
119
+ ? Record<string, never>
120
+ : import(${importPath(typePaths.renderedEmail)}).EmailComponentProps<Component>
121
+
122
+ export type EmailTemplateProps = {
123
+ [Name in EmailTemplateName]: _EmailProps<Awaited<ReturnType<(typeof emailTemplates)[Name]['component']>>['default']>
124
+ }
125
+
126
+ export function renderEmail<Name extends EmailTemplateName>(
127
+ name: Name,
128
+ props: NoInfer<EmailTemplateProps[Name]>,
129
+ ): Promise<import(${importPath(typePaths.renderedEmail)}).RenderedEmail>
130
+ `;
131
+ }
132
+
133
+ export { generateEmailTypes as a, discoverEmailTemplates as d, generateEmailRegistry as g };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/nuxt-email",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Typed transactional email for Nuxt with email-safe Tailwind v4",
5
5
  "keywords": [
6
6
  "email",
@@ -27,6 +27,14 @@
27
27
  "node": "^22.18.0 || ^24.11.0 || ^26.0.0"
28
28
  },
29
29
  "exports": {
30
+ "./build": {
31
+ "types": "./dist/build.d.mts",
32
+ "import": "./dist/build.mjs"
33
+ },
34
+ "./render": {
35
+ "types": "./dist/runtime/render/index.d.ts",
36
+ "import": "./dist/runtime/render/index.js"
37
+ },
30
38
  ".": {
31
39
  "types": "./dist/module.d.mts",
32
40
  "import": "./dist/module.mjs"
@@ -62,12 +70,15 @@
62
70
  "@shikijs/langs": "4.3.1",
63
71
  "@shikijs/themes": "4.3.1",
64
72
  "css-tree": "3.2.1",
73
+ "esbuild": "0.28.1",
65
74
  "h3": "1.15.11",
66
75
  "html-to-text": "9.0.5",
67
76
  "htmlparser2": "^10.1.0",
68
77
  "marked": "15.0.12",
69
78
  "tailwindcss": "4.1.18",
70
- "unplugin-vue": "7.2.0"
79
+ "typescript": "~5.9.3",
80
+ "unplugin-vue": "7.2.0",
81
+ "vue-tsc": "^3.3.3"
71
82
  },
72
83
  "devDependencies": {
73
84
  "@nuxt/eslint-config": "^1.15.2",
@@ -89,10 +100,8 @@
89
100
  "react-dom": "19.2.7",
90
101
  "react-email": "6.9.0",
91
102
  "tsx": "4.23.1",
92
- "typescript": "~5.9.3",
93
103
  "vitest": "^4.1.8",
94
- "vue": "3.5.40",
95
- "vue-tsc": "^3.3.3",
104
+ "vue": "3.5.42",
96
105
  "yaml": "2.9.0"
97
106
  },
98
107
  "scripts": {
@@ -115,7 +124,7 @@
115
124
  "release:prepare": "changelogen --bump --clean --hideAuthorEmail",
116
125
  "release:artifact": "pnpm release:verify --output .release/lupinum-nuxt-email.tgz && node scripts/write-release-manifest.mjs",
117
126
  "lint": "eslint .",
118
- "test": "vitest run --exclude test/preview.test.ts && pnpm test:preview",
127
+ "test": "pnpm prepack && vitest run --exclude test/preview.test.ts && pnpm test:preview",
119
128
  "test:preview": "vitest run test/preview.test.ts",
120
129
  "test:conformance": "vitest run test/conformance",
121
130
  "test:watch": "vitest watch",