@lupinum/nuxt-email 0.1.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 (55) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +63 -18
  3. package/dist/build.d.mts +13 -0
  4. package/dist/build.mjs +218 -0
  5. package/dist/module.d.mts +1 -1
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +54 -145
  8. package/dist/runtime/components/EHtml.d.ts +3 -1
  9. package/dist/runtime/components/EHtml.js +12 -2
  10. package/dist/runtime/components/ELink.js +1 -6
  11. package/dist/runtime/dev-preview/render.get.d.ts +26 -1
  12. package/dist/runtime/dev-preview/render.get.js +2 -2
  13. package/dist/runtime/render/define-email.d.ts +19 -10
  14. package/dist/runtime/render/define-email.js +21 -4
  15. package/dist/runtime/render/errors.d.ts +2 -2
  16. package/dist/runtime/render/errors.js +4 -4
  17. package/dist/runtime/render/index.d.ts +6 -0
  18. package/dist/runtime/render/index.js +3 -0
  19. package/dist/runtime/render/render-email-component.d.ts +3 -6
  20. package/dist/runtime/render/render-email-component.js +16 -16
  21. package/dist/runtime/render/types.d.ts +7 -0
  22. package/dist/runtime/tailwind/css-tree.d.ts +10 -0
  23. package/dist/runtime/tailwind/css-tree.js +11 -0
  24. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.d.ts +8 -38
  25. package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +186 -72
  26. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.d.ts +3 -1
  27. package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +9 -2
  28. package/dist/runtime/tailwind/engine/css/get-custom-properties.d.ts +1 -1
  29. package/dist/runtime/tailwind/engine/css/get-custom-properties.js +1 -1
  30. package/dist/runtime/tailwind/engine/css/is-part-inlinable.d.ts +1 -1
  31. package/dist/runtime/tailwind/engine/css/is-part-inlinable.js +1 -1
  32. package/dist/runtime/tailwind/engine/css/is-rule-inlinable.d.ts +1 -1
  33. package/dist/runtime/tailwind/engine/css/is-rule-inlinable.js +1 -1
  34. package/dist/runtime/tailwind/engine/css/make-inline-styles-for.d.ts +1 -1
  35. package/dist/runtime/tailwind/engine/css/make-inline-styles-for.js +1 -1
  36. package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.d.ts +1 -1
  37. package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.js +1 -1
  38. package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.d.ts +1 -1
  39. package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.js +1 -1
  40. package/dist/runtime/tailwind/engine/css/sanitize-declarations.d.ts +1 -1
  41. package/dist/runtime/tailwind/engine/css/sanitize-declarations.js +1 -1
  42. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.d.ts +1 -1
  43. package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +1 -1
  44. package/dist/runtime/tailwind/engine/css/split-mixed-rule.d.ts +1 -1
  45. package/dist/runtime/tailwind/engine/css/split-mixed-rule.js +1 -1
  46. package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.d.ts +1 -1
  47. package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.js +1 -1
  48. package/dist/runtime/tailwind/engine/index.d.ts +6 -6
  49. package/dist/runtime/tailwind/engine/index.js +70 -7
  50. package/dist/runtime/tailwind/engine/setup-tailwind.d.ts +1 -1
  51. package/dist/runtime/tailwind/engine/setup-tailwind.js +1 -1
  52. package/dist/runtime/tailwind/post-render.js +7 -8
  53. package/dist/shared/nuxt-email.B5ux0eBv.mjs +133 -0
  54. package/dist/types.d.mts +1 -1
  55. package/package.json +18 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
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
+
23
+ ## v1.0.0-beta.1
24
+
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.
26
+
27
+ ### Added
28
+
29
+ - Added synchronous `subject` and authored plain-text `text` metadata to `defineEmail`, including prop-derived functions. Authored text takes precedence over automatic HTML-to-text derivation.
30
+ - Added the type-only `EmailComponentProps<Component>` helper for deterministic, component-checked preview fixtures.
31
+ - Added a baseURL-aware Nuxt DevTools shortcut to the existing development-only `/__email` preview.
32
+ - Added direct Resend and local Nodemailer/Mailpit sending recipes while keeping all delivery behavior application-owned.
33
+
34
+ ### Changed
35
+
36
+ - Rebuilt Tailwind v4 nested-selector and nested `@media`/`@supports` handling so declarations and rules preserve source order, media ranges are downleveled, and unhandled nesting fails instead of emitting raw `&` CSS.
37
+ - Limited emitted keyframes to animations referenced by the current render. Animation remains best-effort progressive enhancement because email-client support is limited.
38
+ - Made `EHtml.lang` a required, non-empty value. `dir` still defaults to `ltr`.
39
+ - Made `ELink` visually neutral by removing its built-in color and underline. `target="_blank"` remains the default and can be overridden.
40
+ - Renamed `EmailRenderError.componentName` to `templateName` and kept the original error as `cause`.
41
+ - Limited module-driven registry rebuilds to structural template and fixture changes; ordinary edits continue through Nitro's normal watcher.
42
+
43
+ ### Breaking changes from v0.1.1
44
+
45
+ - Every `EHtml` now requires an explicit non-empty `lang`.
46
+ - `ELink` no longer supplies color or text-decoration styles. Add those styles explicitly where required.
47
+ - Read `EmailRenderError.templateName` instead of `componentName`; there is no compatibility alias.
48
+ - `defineEmail` rejects an empty object and invalid or asynchronous metadata values. Provide at least one synchronous `subject` or `text` value.
49
+
3
50
  ## v0.1.1
4
51
 
5
52
  [Compare changes](https://github.com/lupinum-dev/nuxt-email/compare/e45025a...v0.1.1)
package/README.md CHANGED
@@ -1,19 +1,31 @@
1
1
  <p align="center">
2
- <img src="docs/public/favicon.svg" width="128" alt="Nuxt Email icon">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/public/icon-dark.svg">
4
+ <source media="(prefers-color-scheme: light)" srcset="docs/public/icon-light.svg">
5
+ <img src="docs/public/icon-light.svg" width="128" alt="Nuxt Email icon">
6
+ </picture>
3
7
  </p>
4
8
 
5
- <h1 align="center">Nuxt Email</h1>
9
+ <h1 align="center">
10
+ <picture>
11
+ <source media="(prefers-color-scheme: dark)" srcset="docs/public/wordmark-dark.svg">
12
+ <source media="(prefers-color-scheme: light)" srcset="docs/public/wordmark-light.svg">
13
+ <img src="docs/public/wordmark-light.svg" width="256" alt="Nuxt Email">
14
+ </picture>
15
+ </h1>
6
16
 
7
17
  <p align="center">Write typed transactional email as Vue components and render it directly in Nuxt.</p>
8
18
 
9
19
  <p align="center">
10
- <a href="https://www.npmjs.com/package/@lupinum/nuxt-email"><img src="https://img.shields.io/npm/v/@lupinum/nuxt-email?label=npm" alt="npm version"></a>
20
+ <a href="https://www.npmjs.com/package/@lupinum/nuxt-email"><img src="https://img.shields.io/npm/v/@lupinum/nuxt-email?color=00DC82" alt="npm version"></a>
11
21
  <a href="https://github.com/lupinum-dev/nuxt-email/actions/workflows/ci.yml"><img src="https://github.com/lupinum-dev/nuxt-email/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
12
- <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
22
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-00DC82" alt="MIT license"></a>
23
+ <a href="https://discord.gg/RPH6SeA36N"><img src="https://img.shields.io/badge/Discord-18181B?logo=discord" alt="Discord"></a>
24
+ <a href="https://deepwiki.com/lupinum-dev/nuxt-email"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
13
25
  </p>
14
26
 
15
27
  > [!WARNING]
16
- > The first scoped release is in preparation. Public APIs can change before version 1.0. 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.
17
29
 
18
30
  ## Why use Nuxt Email?
19
31
 
@@ -40,7 +52,7 @@ CI tests every supported Node major. The release consumer installs Nuxt `4.5.2`
40
52
  Install and register the module:
41
53
 
42
54
  ```bash
43
- pnpm add @lupinum/nuxt-email
55
+ pnpm add @lupinum/nuxt-email@next
44
56
  ```
45
57
 
46
58
  ```ts
@@ -57,19 +69,30 @@ Create `app/emails/welcome.vue`:
57
69
  <script setup lang="ts">
58
70
  import { defineEmail } from '@lupinum/nuxt-email/define-email'
59
71
 
60
- const props = defineProps<{ firstName: string; activationUrl: string }>()
72
+ const props = defineProps<{
73
+ firstName: string
74
+ activationUrl: string
75
+ }>()
61
76
 
62
- defineEmail({ subject: () => `Welcome, ${props.firstName}` })
77
+ defineEmail({
78
+ subject: () => `Welcome aboard, ${props.firstName}`,
79
+ })
63
80
  </script>
64
81
 
65
82
  <template>
66
83
  <ETailwind>
67
84
  <EHtml lang="en">
68
- <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">
69
89
  <EPreview>Your account is ready.</EPreview>
70
- <EContainer class="bg-white p-6">
71
- <EHeading>Welcome, {{ firstName }}</EHeading>
72
- <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>
73
96
  </EContainer>
74
97
  </EBody>
75
98
  </EHtml>
@@ -88,7 +111,22 @@ export default defineEventHandler(async () => {
88
111
  })
89
112
  ```
90
113
 
91
- 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).
115
+
116
+ ## Discord
117
+
118
+ Join the Lupinum OSS community to discuss Nuxt Email, ask questions, and share
119
+ what you build.
120
+
121
+ <p align="center">
122
+ <a href="https://discord.gg/RPH6SeA36N">
123
+ <picture>
124
+ <source media="(prefers-color-scheme: dark)" srcset="docs/public/discord-dark.svg">
125
+ <source media="(prefers-color-scheme: light)" srcset="docs/public/discord-light.svg">
126
+ <img src="docs/public/discord-light.svg" width="500" alt="Join the Lupinum OSS Discord">
127
+ </picture>
128
+ </a>
129
+ </p>
92
130
 
93
131
  ## Email components
94
132
 
@@ -114,6 +152,16 @@ Run the Nuxt application and open `/__email`. The development-only page shows th
114
152
 
115
153
  Add one sibling fixture file for deterministic sample props. Preview routes and fixtures are excluded from production builds.
116
154
 
155
+ ```ts
156
+ import type { EmailComponentProps } from '@lupinum/nuxt-email'
157
+ import type WelcomeEmail from './welcome.vue'
158
+
159
+ export default {
160
+ firstName: 'Ada',
161
+ activationUrl: 'https://example.com/activate',
162
+ } satisfies EmailComponentProps<typeof WelcomeEmail>
163
+ ```
164
+
117
165
  ## Testing
118
166
 
119
167
  Render a Vue email without starting Nuxt:
@@ -138,14 +186,11 @@ Complete the [manual client QA checklist](https://github.com/lupinum-dev/nuxt-em
138
186
 
139
187
  ## Package exports
140
188
 
141
- - `@lupinum/nuxt-email` provides the Nuxt module and the `RenderedEmail` type.
142
- - `@lupinum/nuxt-email/define-email` provides typed template metadata.
143
- - `@lupinum/nuxt-email/testing` provides standalone component rendering.
144
- - `@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.
145
190
 
146
191
  ## Documentation
147
192
 
148
- 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), [testing guide](https://nuxt-email.lupinum.com/docs/guides/testing-your-emails), and [preview guide](https://nuxt-email.lupinum.com/docs/guides/preview-workflow).
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).
149
194
 
150
195
  See the [changelog](CHANGELOG.md) for released changes.
151
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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- export { RenderedEmail } from '../dist/runtime/render/types.js';
2
+ export { EmailComponentProps, RenderedEmail } from '../dist/runtime/render/types.js';
3
3
 
4
4
  interface CodeBlockOptions {
5
5
  languages: readonly string[];
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.5.1 <5"
6
6
  },
7
- "version": "0.1.1",
7
+ "version": "1.0.0-beta.2",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"