@samva/vite 0.1.1 → 0.2.0
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.
- package/AGENTS.md +70 -1
- package/README.md +92 -44
- package/dist/build.d.ts +47 -28
- package/dist/build.js +166 -67
- package/dist/editor/assets/index-Cg4x6ewh.css +2 -0
- package/dist/editor/assets/index-Cot9JDdw.js +30 -0
- package/dist/editor/index.html +2 -2
- package/dist/email.d.ts +58 -0
- package/dist/email.js +116 -0
- package/dist/emit.d.ts +13 -30
- package/dist/emit.js +29 -123
- package/dist/index.d.ts +1 -4
- package/dist/index.js +480 -305
- package/dist/project.d.ts +116 -0
- package/dist/project.js +294 -0
- package/dist/theme-CBF6Czjm.js +50 -0
- package/package.json +28 -20
- package/dist/editor/assets/authoring-editor-CSA7Zv4v-B3TgD9LK.js +0 -145
- package/dist/editor/assets/index-C1g6_O2Y.js +0 -86
- package/dist/editor/assets/index-j-Fc0EML.css +0 -2
- package/dist/theme-CfJe8s3k.js +0 -138
package/AGENTS.md
CHANGED
|
@@ -5,4 +5,73 @@ Install `@samva/markup`, `@samva/vite`, and the Vite peer in the template projec
|
|
|
5
5
|
programmatic template builds and `@samva/vite/emit` only for module-preview integration.
|
|
6
6
|
|
|
7
7
|
The CLI loads `@samva/vite/build` from the current project. Keep these authoring dependencies in the
|
|
8
|
-
project lockfile so local preview and
|
|
8
|
+
project lockfile so local preview, check, and build use the same compiler version as the project.
|
|
9
|
+
|
|
10
|
+
Hosted synchronization is ordinary Git. A CLI-created project has a repository-local Samva
|
|
11
|
+
credential helper, and `samva templates publish` accepts only a clean exact commit reachable from
|
|
12
|
+
the fetched `origin` default branch. Publish creates an immutable receipt; it does not push or
|
|
13
|
+
rewrite history.
|
|
14
|
+
|
|
15
|
+
Template discovery is `.tsx` only. The local email editor writes the canonical TSX entry with a
|
|
16
|
+
revision check and a fresh comparison against disk; SMS and WhatsApp stay read-only. Every discovered entry is imported once through the dev
|
|
17
|
+
server's own SSR graph, and a default export that is a template definition takes the email lane —
|
|
18
|
+
compiled and rendered through `compileEmailProject`, so a local render cannot drift from a published
|
|
19
|
+
one. Email requires an executable definition. Structured SMS/WhatsApp entries emit SML, and
|
|
20
|
+
for SMS and WhatsApp an invalid current TSX keeps and visibly labels the last successful emission
|
|
21
|
+
while the same file remains discovered; deleting the file removes it.
|
|
22
|
+
|
|
23
|
+
The email document is one executed render of one declared fixture: the fixture names the template
|
|
24
|
+
declares, the fixture being viewed, the render stamped with `data-samva-instance`, and the compile
|
|
25
|
+
and client findings taken from rendering every declared fixture as it will be delivered. An entry
|
|
26
|
+
that does not build keeps its place with a null render and the findings that stopped it. Its
|
|
27
|
+
revision is a digest of authored source and what the declared fixtures render, so source locations
|
|
28
|
+
cannot stay pinned after a formatting-only edit. It stays put when only the viewed fixture changes. The dev server is not an https origin, so a local
|
|
29
|
+
render points at the editor's own asset route by path — every render here is a preview or the input
|
|
30
|
+
to a finding, and the base a publication serves from is a build input rather than authored.
|
|
31
|
+
|
|
32
|
+
Object email projects compile through `@samva/vite/email`. It is an adapter: the whole compile step
|
|
33
|
+
is `@samva/markup/compiler`'s `compileEmailProject`, the same call the hosted build makes, and this
|
|
34
|
+
package owns only module resolution and evaluation. The asset base is an explicit input, so a
|
|
35
|
+
build and an export can address the same content from different origins. Both entry points evaluate
|
|
36
|
+
the compiler's generated wrapper rather than the authored entry: the render has to happen inside the
|
|
37
|
+
graph the project's stylesheets registered into.
|
|
38
|
+
|
|
39
|
+
`createEmailRenderer` keeps one evaluator across renders, as a dev server does, and gives each
|
|
40
|
+
project its own id namespace so their modules coexist. Anything the compiler rewrote into
|
|
41
|
+
JavaScript — a stylesheet, an imported asset — is served under a `.js` id; its authored extension
|
|
42
|
+
would be claimed by Vite's CSS or asset pipeline. A caller that reloads the same project passes a
|
|
43
|
+
stable `namespace` so ids survive the reload and `invalidate()` is what makes changed source take
|
|
44
|
+
effect; omitting it takes a fresh namespace, which is what isolates unrelated projects.
|
|
45
|
+
|
|
46
|
+
`@samva/vite/project` is the folder-of-templates surface. A project holds many definitions with
|
|
47
|
+
shared components and CSS between them; which `.tsx` files are templates is decided by evaluating
|
|
48
|
+
the compiler's generated wrapper and asking whether the default export is a template definition, so
|
|
49
|
+
an ordinary helper module needs no marker and no exclusion list. Identity is the id the definition
|
|
50
|
+
declares, never its path, and two definitions declaring one id is a reported error. Fixtures are
|
|
51
|
+
validated against the template's own schema before they render, so a bad fixture is a source defect
|
|
52
|
+
the author sees locally.
|
|
53
|
+
|
|
54
|
+
`exportEmailProject` renders in the compiler's export mode (`relativeUrls: true`). It writes one
|
|
55
|
+
page at `templates/<id>/<fixture>.html` and every asset once under `assets/`, so its default
|
|
56
|
+
`../../assets` base is the path from a page's own directory to those bytes. Delivery keeps
|
|
57
|
+
refusing a relative reference — an inbox has no directory to resolve one against — so an export
|
|
58
|
+
destined for sending names the https origin it will be served from instead. Either way the export
|
|
59
|
+
writes each asset under its content address and records the base, so the manifest is the whole
|
|
60
|
+
mapping.
|
|
61
|
+
|
|
62
|
+
A refused render reports against the one fixture rather than throwing at the caller: one template
|
|
63
|
+
that cannot render must not take the rest of the catalog with it.
|
|
64
|
+
|
|
65
|
+
The evaluator roots itself at this package's own directory and refuses an ambient Vite config.
|
|
66
|
+
Virtual ids have no directory, so bare imports resolve against the server root; rooting here is what
|
|
67
|
+
makes `@samva/markup` resolve under a per-package install as well as a hoisted one.
|
|
68
|
+
|
|
69
|
+
A golden-digest test in `tests/email-parity.test.ts` pins this adapter's output against the same
|
|
70
|
+
value the hosted build asserts in `apps/agents/tests/workerd/email-parity.test.ts`.
|
|
71
|
+
|
|
72
|
+
## Verification
|
|
73
|
+
|
|
74
|
+
`bun run --filter @samva/vite test:browser` builds the plugin and embedded editor, then runs the
|
|
75
|
+
Chromium source-editing integration tests. `test:browser:cross` runs Chromium, Firefox, and WebKit.
|
|
76
|
+
These tests own temporary template projects and plugin servers; the editor library's browser suite
|
|
77
|
+
tests its standalone host separately.
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @samva/vite
|
|
2
2
|
|
|
3
3
|
The unified local editor for Samva email, SMS, and WhatsApp templates. One Vite
|
|
4
|
-
plugin discovers `.
|
|
5
|
-
|
|
4
|
+
plugin discovers `.tsx` files, evaluates them through the Samva JSX runtime,
|
|
5
|
+
applies the project theme, and serves the full visual editor with HMR.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,9 +12,8 @@ Keep the authoring library and editor in the project, with Vite provided as the
|
|
|
12
12
|
npm install --save-dev @samva/markup @samva/vite vite
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`samva templates init`
|
|
16
|
-
|
|
17
|
-
untouched.
|
|
15
|
+
`samva templates init` creates a project from the canonical starter, which contains these
|
|
16
|
+
dependencies and an equivalent `vite.config.ts`. Existing project scripts remain untouched.
|
|
18
17
|
|
|
19
18
|
```ts
|
|
20
19
|
import { samvaEditor } from "@samva/vite";
|
|
@@ -44,65 +43,114 @@ to the rest of the Vite middleware stack.
|
|
|
44
43
|
|
|
45
44
|
## Authoring files
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
helpers and constants. A file without a default export is excluded with a
|
|
53
|
-
warning; an invalid default export is a diagnostic.
|
|
46
|
+
Email `.tsx` entries default-export `defineEmail({ id, schema, fixtures, render })` from
|
|
47
|
+
`@samva/markup/template`. Use the `@samva/markup/email` JSX runtime and primitives from
|
|
48
|
+
`@samva/markup/email/components`. Ordinary synchronous helpers, conditions and loops execute
|
|
49
|
+
against actual validated JSON. Shared helper modules are excluded from the template catalog;
|
|
50
|
+
ids are stable independently of paths and must be unique within the project.
|
|
54
51
|
|
|
55
52
|
```tsx
|
|
56
|
-
|
|
57
|
-
import
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export default
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
/** @jsxImportSource @samva/markup/email */
|
|
54
|
+
import { Schema } from "effect";
|
|
55
|
+
import { Email, Section } from "@samva/markup/email/components";
|
|
56
|
+
import { defineEmail } from "@samva/markup/template";
|
|
57
|
+
|
|
58
|
+
export default defineEmail({
|
|
59
|
+
id: "welcome",
|
|
60
|
+
schema: Schema.toStandardJSONSchemaV1(
|
|
61
|
+
Schema.Struct({ firstName: Schema.String, items: Schema.Array(Schema.String) }),
|
|
62
|
+
),
|
|
63
|
+
fixtures: { basic: { firstName: "Ada", items: ["Your first project"] } },
|
|
64
|
+
render: (input) => ({
|
|
65
|
+
subject: `Welcome, ${input.firstName}`,
|
|
66
|
+
preheader: "Your account is ready",
|
|
67
|
+
body: (
|
|
68
|
+
<Email>
|
|
69
|
+
<Section>
|
|
70
|
+
<h1>Welcome, {input.firstName}</h1>
|
|
71
|
+
{input.items.length > 0 && (
|
|
72
|
+
<ul>
|
|
73
|
+
{input.items.map((item) => (
|
|
74
|
+
<li key={item}>{item}</li>
|
|
75
|
+
))}
|
|
76
|
+
</ul>
|
|
77
|
+
)}
|
|
78
|
+
</Section>
|
|
79
|
+
</Email>
|
|
80
|
+
),
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
70
83
|
```
|
|
71
84
|
|
|
72
|
-
The editor
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
The email editor previews declared fixtures and offers source-aware edits where the source intent
|
|
86
|
+
is unambiguous. Source is canonical. Failed email entries remain visible with diagnostics and a
|
|
87
|
+
null render. SMS and WhatsApp remain read-only structured previews; an invalid edit retains their
|
|
88
|
+
last successful emission and labels it stale. Deleting an entry removes its preview.
|
|
89
|
+
|
|
90
|
+
The browser canvas is a structural preview of Samva's modern and Outlook-safe
|
|
91
|
+
compiler output. It does not emulate the exact Gmail, Outlook, Apple Mail, or
|
|
92
|
+
Yahoo rendering engines. Use compatibility findings and inbox tests for
|
|
93
|
+
client-specific proof.
|
|
76
94
|
|
|
77
95
|
## Options
|
|
78
96
|
|
|
79
97
|
| Option | Type | Default | Description |
|
|
80
98
|
| -------------- | ----------------- | ------------- | ---------------------------------------------------------------- |
|
|
81
|
-
| `templatesDir` | `string` | `"emails"` | Recursive `.
|
|
99
|
+
| `templatesDir` | `string` | `"emails"` | Recursive `.tsx` template directory. |
|
|
82
100
|
| `route` | `string` | `"/"` | Absolute editor UI route; API and assets are mounted beneath it. |
|
|
83
101
|
| `theme` | `string \| false` | `"theme.css"` | Project theme file, or `false` to disable discovery. |
|
|
84
|
-
| `compile` | `CompileOptions` | — | Extra compile options applied to local rendering and validation. |
|
|
85
102
|
|
|
86
|
-
## Theme and
|
|
103
|
+
## Theme, check, and publish
|
|
87
104
|
|
|
88
105
|
A root `theme.css` uses Tailwind v4 `@theme` syntax. Theme changes recompile
|
|
89
106
|
local previews while preserving the last valid theme when an edit is temporarily
|
|
90
107
|
invalid.
|
|
91
108
|
|
|
92
|
-
`samva templates
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
`samva templates check` and `buildTemplates()` execute every email fixture and validate its
|
|
110
|
+
actual input. `buildTemplates()` returns `files`, each with a `preview.kind` discriminator:
|
|
111
|
+
`email` contains input schema, compiled module graph, actual fixture subject/HTML/text and
|
|
112
|
+
structured diagnostics; `structured` contains the SMS/WhatsApp preview. Helper modules have no
|
|
113
|
+
file entry. An email requires at least one fixture.
|
|
114
|
+
|
|
115
|
+
`samva templates build --dir emails --out dist/templates.json` writes the CLI's version 2 local
|
|
116
|
+
artifact. Check writes no files. `check --watch` observes changes; `--jsonl` emits one observation
|
|
117
|
+
per rebuild and `--json` is one-shot. A successful fixture check does not prove every possible
|
|
118
|
+
input branch.
|
|
95
119
|
|
|
96
|
-
|
|
120
|
+
Imported email assets require `assetBase` in `buildTemplates()` or `--asset-base` in CLI checks
|
|
121
|
+
and builds, naming their actual HTTPS serving base. No credentials are needed for local work.
|
|
97
122
|
|
|
98
|
-
|
|
123
|
+
For a hosted project, commit and push the checked TSX tree with ordinary Git, then publish one exact
|
|
124
|
+
commit:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
git add .
|
|
128
|
+
git commit -m "feat: update welcome email"
|
|
129
|
+
git push origin main
|
|
130
|
+
samva templates publish --commit HEAD
|
|
131
|
+
```
|
|
99
132
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
133
|
+
The CLI's repository-local credential helper obtains a short-lived Artifact credential through
|
|
134
|
+
Samva's credential protocol when Git needs one. It validates the repository host/path and does not
|
|
135
|
+
store the token in Git config or the remote URL. `templates publish` requires a clean worktree and
|
|
136
|
+
an exact commit reachable from the fetched `origin` default branch; it creates an immutable
|
|
137
|
+
publication receipt and does not push or rewrite history.
|
|
138
|
+
|
|
139
|
+
Hosted builds consume the committed Bun text lockfile version 2 or npm lockfile version 3.
|
|
140
|
+
Keep dependencies and the supported lockfile in the project. Sending selects an exact immutable
|
|
141
|
+
publication by default; later source edits do not change that publication.
|
|
142
|
+
|
|
143
|
+
## Local export
|
|
105
144
|
|
|
106
145
|
```ts
|
|
107
|
-
|
|
146
|
+
import { exportEmailProject } from "@samva/vite/project";
|
|
147
|
+
|
|
148
|
+
const receipt = await exportEmailProject({ dir: ".", out: "dist/email" });
|
|
108
149
|
```
|
|
150
|
+
|
|
151
|
+
Export writes `templates/<id>/<fixture>.html` and `.txt`, content-addressed files under `assets/`,
|
|
152
|
+
and `catalog.json` with the asset mapping and findings. The default `../../assets` base resolves
|
|
153
|
+
from each exported page. For delivery, supply the actual HTTPS `assetBase` and serve those bytes
|
|
154
|
+
there; relative URLs are for local export. External HTTPS images stay external references.
|
|
155
|
+
|
|
156
|
+
The runnable `examples/email-starter` workspace exercises the authoring setup.
|
package/dist/build.d.ts
CHANGED
|
@@ -1,42 +1,61 @@
|
|
|
1
1
|
import { ModulePreview } from "./emit.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { CompiledEmailProject } from "@samva/markup/compiler";
|
|
3
|
+
import { EmailDiagnostic } from "@samva/markup/email/diagnostics";
|
|
4
4
|
//#region src/build.d.ts
|
|
5
|
+
/** One declared input rendered through executable email source. */
|
|
6
|
+
interface EmailFixtureBuild {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly input: unknown;
|
|
9
|
+
readonly ok: boolean;
|
|
10
|
+
readonly subject?: string | undefined;
|
|
11
|
+
readonly preheader?: string | undefined;
|
|
12
|
+
readonly html?: string | undefined;
|
|
13
|
+
readonly text?: string | undefined;
|
|
14
|
+
readonly diagnostics: readonly EmailDiagnostic[];
|
|
15
|
+
}
|
|
16
|
+
/** Executable email artifacts retain their input contract, module graph and actual fixture output. */
|
|
17
|
+
interface EmailBuildPreview {
|
|
18
|
+
readonly kind: "email";
|
|
19
|
+
readonly ok: boolean;
|
|
20
|
+
readonly compilerVersion: string;
|
|
21
|
+
readonly diagnostics: readonly EmailDiagnostic[];
|
|
22
|
+
readonly templates: readonly {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly channel: "email";
|
|
25
|
+
readonly ok: boolean;
|
|
26
|
+
readonly source: string;
|
|
27
|
+
readonly inputSchema: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly compiled: CompiledEmailProject;
|
|
29
|
+
readonly fixtures: readonly EmailFixtureBuild[];
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
type TemplateBuildPreview = EmailBuildPreview | (ModulePreview & {
|
|
33
|
+
readonly kind: "structured";
|
|
34
|
+
});
|
|
5
35
|
interface BuildTemplatesResult {
|
|
6
|
-
/**
|
|
36
|
+
/** Authored entry paths, sorted; helper modules have no entry here. */
|
|
7
37
|
readonly files: ReadonlyArray<{
|
|
8
38
|
readonly id: string;
|
|
9
|
-
readonly kind: "tsx"
|
|
10
|
-
readonly preview:
|
|
39
|
+
readonly kind: "tsx";
|
|
40
|
+
readonly preview: TemplateBuildPreview;
|
|
11
41
|
}>;
|
|
12
|
-
/**
|
|
13
|
-
* Project theme.css parse diagnostics (spans into theme.css). `buildTemplates`
|
|
14
|
-
* always sets it (empty when the theme parsed or there is no theme file);
|
|
15
|
-
* optional so callers constructing a result by hand needn't supply it.
|
|
16
|
-
*/
|
|
17
|
-
readonly themeDiagnostics?: ReadonlyArray<Diagnostic> | undefined;
|
|
18
42
|
}
|
|
19
43
|
interface BuildTemplatesOptions {
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Project root to discover `theme.css` from (design brief §7e), themed into
|
|
23
|
-
* every compiled preview as the org-token layer. Defaults to the parent of
|
|
24
|
-
* `dir` (templates live under the project root, e.g. `emails/`).
|
|
25
|
-
*/
|
|
44
|
+
/** Project root containing shared imports and theme.css; defaults to the parent of dir. */
|
|
26
45
|
readonly root?: string | undefined;
|
|
27
|
-
/**
|
|
28
|
-
* Theme file override, relative to `root`. `false` disables theme discovery.
|
|
29
|
-
* Default: `theme.css`.
|
|
30
|
-
*/
|
|
31
46
|
readonly theme?: string | false | undefined;
|
|
47
|
+
/** Required for imported email assets. Must be their actual HTTPS serving base. */
|
|
48
|
+
readonly assetBase?: string | undefined;
|
|
49
|
+
}
|
|
50
|
+
interface TemplateBuildSession {
|
|
51
|
+
readonly build: () => Promise<BuildTemplatesResult>;
|
|
52
|
+
readonly subscribe: (listener: (path: string) => void) => () => void;
|
|
53
|
+
readonly close: () => Promise<void>;
|
|
32
54
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Discovers and evaluates every `.tsx`/`.samva` template under `dir`, returning
|
|
35
|
-
* emitted SML + compiled artifacts per template — themed by the project's
|
|
36
|
-
* `theme.css` if present. Throws if `vite` is not installed (the optional peer) —
|
|
37
|
-
* the caller surfaces that as a user error.
|
|
38
|
-
*/
|
|
55
|
+
/** Execute each email definition's fixtures; compile structured SMS/WhatsApp through their channel compiler. */
|
|
39
56
|
declare const buildTemplates: (dir: string, options?: BuildTemplatesOptions) => Promise<BuildTemplatesResult>;
|
|
57
|
+
/** A watcher and module evaluator over the project's installed authoring toolchain. */
|
|
58
|
+
declare const createTemplateBuildSession: (dir: string, options?: BuildTemplatesOptions) => Promise<TemplateBuildSession>;
|
|
40
59
|
//#endregion
|
|
41
|
-
export { BuildTemplatesOptions, BuildTemplatesResult, buildTemplates };
|
|
60
|
+
export { BuildTemplatesOptions, BuildTemplatesResult, EmailBuildPreview, EmailFixtureBuild, TemplateBuildPreview, TemplateBuildSession, buildTemplates, createTemplateBuildSession };
|
|
42
61
|
//# sourceMappingURL=build.d.ts.map
|
package/dist/build.js
CHANGED
|
@@ -1,87 +1,186 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { COMPILER_VERSION,
|
|
3
|
-
import {
|
|
1
|
+
import { n as resolveThemePath, r as discoverTemplateFiles, t as readProjectThemeCss } from "./theme-CBF6Czjm.js";
|
|
2
|
+
import { COMPILER_VERSION, emitModulePreview } from "./emit.js";
|
|
3
|
+
import { createEmailRenderer } from "./email.js";
|
|
4
|
+
import { isTemplateDefinition, readEmailProject, renderEmailFixtures } from "./project.js";
|
|
5
|
+
import { dirname, parse, relative, resolve, sep } from "node:path";
|
|
4
6
|
//#region src/build.ts
|
|
5
|
-
/**
|
|
6
|
-
* Discovers and evaluates every `.tsx`/`.samva` template under `dir`, returning
|
|
7
|
-
* emitted SML + compiled artifacts per template — themed by the project's
|
|
8
|
-
* `theme.css` if present. Throws if `vite` is not installed (the optional peer) —
|
|
9
|
-
* the caller surfaces that as a user error.
|
|
10
|
-
*/
|
|
7
|
+
/** Execute each email definition's fixtures; compile structured SMS/WhatsApp through their channel compiler. */
|
|
11
8
|
const buildTemplates = async (dir, options = {}) => {
|
|
12
|
-
const
|
|
13
|
-
const root = options.root ?? dirname(templatesDir);
|
|
14
|
-
const theme = await loadProjectTheme(resolveThemePath(root, options.theme));
|
|
15
|
-
const compile = withThemeLayer(options.compile, theme.tokens);
|
|
16
|
-
const files = await discoverTemplateFiles(templatesDir);
|
|
17
|
-
const server = files.filter((file) => file.kind === "tsx").length > 0 ? await createEvaluator(templatesDir) : void 0;
|
|
9
|
+
const session = await createTemplateBuildSession(dir, options);
|
|
18
10
|
try {
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
11
|
+
return await session.build();
|
|
12
|
+
} finally {
|
|
13
|
+
await session.close();
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const emailFinding = (code, message) => ({
|
|
17
|
+
code,
|
|
18
|
+
severity: "error",
|
|
19
|
+
message,
|
|
20
|
+
origins: []
|
|
21
|
+
});
|
|
22
|
+
const emailFailure = (cause, code = "email-build-error") => ({
|
|
23
|
+
kind: "email",
|
|
24
|
+
ok: false,
|
|
25
|
+
compilerVersion: COMPILER_VERSION,
|
|
26
|
+
templates: [],
|
|
27
|
+
diagnostics: Array.isArray(cause?.diagnostics) ? cause.diagnostics : [emailFinding(code, cause instanceof Error ? cause.message : String(cause))]
|
|
28
|
+
});
|
|
29
|
+
/** A watcher and module evaluator over the project's installed authoring toolchain. */
|
|
30
|
+
const createTemplateBuildSession = async (dir, options = {}) => {
|
|
31
|
+
const templatesDir = resolve(dir);
|
|
32
|
+
const root = resolve(options.root ?? dirname(templatesDir));
|
|
33
|
+
const themePath = resolveThemePath(root, options.theme);
|
|
34
|
+
await discoverTemplateFiles(templatesDir);
|
|
35
|
+
const server = await createEvaluator(templatesDir);
|
|
36
|
+
server.watcher.add(root);
|
|
37
|
+
if (themePath !== void 0) server.watcher.add(themePath);
|
|
38
|
+
let closed = false;
|
|
39
|
+
let emailRenderer;
|
|
40
|
+
let previousTheme;
|
|
41
|
+
const build = async () => {
|
|
42
|
+
server.moduleGraph.invalidateAll();
|
|
43
|
+
const themeCss = await readProjectThemeCss(themePath);
|
|
44
|
+
if (emailRenderer !== void 0 && themeCss !== previousTheme) {
|
|
45
|
+
await emailRenderer.close();
|
|
46
|
+
emailRenderer = void 0;
|
|
47
|
+
}
|
|
48
|
+
previousTheme = themeCss;
|
|
49
|
+
emailRenderer?.invalidate();
|
|
50
|
+
const files = await discoverTemplateFiles(templatesDir);
|
|
51
|
+
const sourceFiles = await readEmailProject(root);
|
|
52
|
+
const entries = [];
|
|
53
|
+
const emailIds = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const evaluated = await server.ssrLoadModule(file.path).then((moduleExports) => ({
|
|
56
|
+
ok: true,
|
|
57
|
+
moduleExports
|
|
58
|
+
}), (cause) => ({
|
|
59
|
+
ok: false,
|
|
60
|
+
cause
|
|
61
|
+
}));
|
|
62
|
+
if (!evaluated.ok) {
|
|
63
|
+
entries.push({
|
|
64
|
+
id: file.id,
|
|
65
|
+
kind: file.kind,
|
|
66
|
+
preview: emailFailure(evaluated.cause)
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const definition = evaluated.moduleExports.default;
|
|
71
|
+
if (definition === void 0) continue;
|
|
72
|
+
if (isTemplateDefinition(definition)) {
|
|
73
|
+
const entryPath = relative(root, file.path).split(sep).join("/");
|
|
74
|
+
const previous = emailIds.get(definition.id);
|
|
75
|
+
if (previous !== void 0) {
|
|
76
|
+
entries.push({
|
|
33
77
|
id: file.id,
|
|
34
78
|
kind: file.kind,
|
|
35
|
-
preview
|
|
36
|
-
};
|
|
79
|
+
preview: emailFailure(`Duplicate template id ${definition.id}: ${previous} and ${file.id}`, "duplicate-template-id")
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
37
82
|
}
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
83
|
+
emailIds.set(definition.id, file.id);
|
|
84
|
+
const preview = await (async () => {
|
|
85
|
+
emailRenderer ??= await createEmailRenderer({
|
|
86
|
+
assetBase: options.assetBase ?? "",
|
|
87
|
+
...themeCss === void 0 ? {} : { tailwind: { css: themeCss } }
|
|
88
|
+
});
|
|
89
|
+
const loaded = await emailRenderer.load(sourceFiles, entryPath, entryPath);
|
|
90
|
+
const template = loaded.template;
|
|
91
|
+
if (!isTemplateDefinition(template)) return emailFailure(`${file.id} does not export defineEmail`);
|
|
92
|
+
const fixtures = renderEmailFixtures({
|
|
93
|
+
id: template.id,
|
|
94
|
+
entryPath,
|
|
95
|
+
definition: template,
|
|
96
|
+
loaded
|
|
97
|
+
}).map((fixture) => ({
|
|
98
|
+
name: fixture.fixture,
|
|
99
|
+
input: template.fixtures[fixture.fixture],
|
|
100
|
+
ok: fixture.rendered !== void 0 && !fixture.diagnostics.some((finding) => finding.severity === "error"),
|
|
101
|
+
...fixture.rendered === void 0 ? {} : {
|
|
102
|
+
subject: fixture.rendered.subject,
|
|
103
|
+
preheader: fixture.rendered.preheader,
|
|
104
|
+
html: fixture.rendered.html,
|
|
105
|
+
text: fixture.rendered.text
|
|
106
|
+
},
|
|
107
|
+
diagnostics: fixture.diagnostics
|
|
108
|
+
}));
|
|
109
|
+
const diagnostics = [...loaded.compiled.diagnostics];
|
|
110
|
+
if (fixtures.length === 0) diagnostics.push(emailFinding("missing-fixtures", `${template.id} must declare at least one fixture for check and build.`));
|
|
111
|
+
if (loaded.compiled.assets.length > 0 && options.assetBase === void 0) diagnostics.push(emailFinding("missing-asset-base", "Imported email assets require --asset-base with their HTTPS serving origin."));
|
|
112
|
+
if (loaded.compiled.assets.length > 0 && options.assetBase !== void 0 && !/^https:\/\//i.test(options.assetBase)) diagnostics.push(emailFinding("invalid-asset-base", "Email check and build require an HTTPS --asset-base; relative URLs belong to templates export."));
|
|
113
|
+
const ok = !diagnostics.some((finding) => finding.severity === "error") && fixtures.every((fixture) => fixture.ok);
|
|
114
|
+
return {
|
|
115
|
+
kind: "email",
|
|
116
|
+
ok,
|
|
51
117
|
compilerVersion: COMPILER_VERSION,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
118
|
+
diagnostics,
|
|
119
|
+
templates: [{
|
|
120
|
+
name: template.id,
|
|
121
|
+
channel: "email",
|
|
122
|
+
ok,
|
|
123
|
+
source: String(sourceFiles[entryPath]),
|
|
124
|
+
inputSchema: template.schema.schema,
|
|
125
|
+
compiled: loaded.compiled,
|
|
126
|
+
fixtures
|
|
60
127
|
}]
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
128
|
+
};
|
|
129
|
+
})().catch(emailFailure);
|
|
130
|
+
entries.push({
|
|
64
131
|
id: file.id,
|
|
65
132
|
kind: file.kind,
|
|
66
|
-
preview
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
133
|
+
preview
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const preview = emitModulePreview(parse(file.id).name, evaluated.moduleExports);
|
|
138
|
+
if (preview.templates.length === 0) continue;
|
|
139
|
+
if (preview.templates.some((template) => /^\s*<Email(?:\s|>)/.test(template.source ?? ""))) entries.push({
|
|
140
|
+
id: file.id,
|
|
141
|
+
kind: file.kind,
|
|
142
|
+
preview: emailFailure(`${file.id}: email templates must default-export defineEmail with an input schema and fixtures.`, "email-requires-object-template")
|
|
143
|
+
});
|
|
144
|
+
else entries.push({
|
|
145
|
+
id: file.id,
|
|
146
|
+
kind: file.kind,
|
|
147
|
+
preview: {
|
|
148
|
+
...preview,
|
|
149
|
+
kind: "structured"
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { files: entries };
|
|
154
|
+
};
|
|
155
|
+
const subscribe = (listener) => {
|
|
156
|
+
const onChange = (_event, path) => listener(path);
|
|
157
|
+
server.watcher.on("all", onChange);
|
|
158
|
+
return () => server.watcher.off("all", onChange);
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
build,
|
|
162
|
+
subscribe,
|
|
163
|
+
close: async () => {
|
|
164
|
+
if (closed) return;
|
|
165
|
+
closed = true;
|
|
166
|
+
try {
|
|
167
|
+
await emailRenderer?.close();
|
|
168
|
+
} finally {
|
|
169
|
+
await server.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
74
173
|
};
|
|
75
174
|
const createEvaluator = async (root) => {
|
|
76
175
|
const vite = await import("vite").catch(() => void 0);
|
|
77
|
-
if (vite === void 0) throw new Error("`vite` is required for
|
|
176
|
+
if (vite === void 0) throw new Error("`vite` is required for template authoring. Install it as a dev dependency.");
|
|
78
177
|
return vite.createServer({
|
|
79
178
|
root,
|
|
179
|
+
configFile: false,
|
|
80
180
|
logLevel: "silent",
|
|
81
181
|
server: {
|
|
82
182
|
middlewareMode: true,
|
|
83
|
-
hmr: false
|
|
84
|
-
watch: null
|
|
183
|
+
hmr: false
|
|
85
184
|
},
|
|
86
185
|
oxc: { jsx: {
|
|
87
186
|
runtime: "automatic",
|
|
@@ -94,6 +193,6 @@ const createEvaluator = async (root) => {
|
|
|
94
193
|
});
|
|
95
194
|
};
|
|
96
195
|
//#endregion
|
|
97
|
-
export { buildTemplates };
|
|
196
|
+
export { buildTemplates, createTemplateBuildSession };
|
|
98
197
|
|
|
99
198
|
//# sourceMappingURL=build.js.map
|