@samva/markup 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 +25 -5
- package/README.md +92 -26
- package/dist/{ast-DAhnAvQN.d.ts → ast-63I-VLQx.d.ts} +15 -2
- package/dist/ast.d.ts +2 -2
- package/dist/ast.js +15 -2
- package/dist/{checks-CW0nFj5n.js → checks-DXuERO4B.js} +37 -3
- package/dist/checks.d.ts +8 -1
- package/dist/checks.js +2 -2
- package/dist/compile-sms.d.ts +2 -2
- package/dist/compile-whatsapp.d.ts +2 -2
- package/dist/compile.d.ts +9 -84
- package/dist/compile.js +11 -920
- package/dist/compiler.d.ts +301 -0
- package/dist/compiler.js +1389 -0
- package/dist/components.d.ts +4 -4
- package/dist/conditionals.js +1 -1
- package/dist/corpus-Dz5irJ9x.d.ts +161 -0
- package/dist/diagnostics-Djd9ckLg.d.ts +90 -0
- package/dist/diagnostics.js +6 -3
- package/dist/elements-DtwKlFVH.d.ts +820 -0
- package/dist/email/components.d.ts +119 -0
- package/dist/email/components.js +207 -0
- package/dist/email/diagnostics.d.ts +2 -0
- package/dist/email/diagnostics.js +56 -0
- package/dist/email/elements.d.ts +2 -0
- package/dist/email/elements.js +299 -0
- package/dist/email/html.d.ts +42 -0
- package/dist/email/html.js +266 -0
- package/dist/email/jsx-dev-runtime.d.ts +26 -0
- package/dist/email/jsx-dev-runtime.js +12 -0
- package/dist/email/jsx-runtime.d.ts +2 -0
- package/dist/email/jsx-runtime.js +60 -0
- package/dist/email/render.d.ts +36 -0
- package/dist/email/render.js +6 -0
- package/dist/email/selection.d.ts +102 -0
- package/dist/email/selection.js +143 -0
- package/dist/email/source-edit.d.ts +67 -0
- package/dist/email/source-edit.js +185 -0
- package/dist/email/stylesheets.d.ts +2 -0
- package/dist/email/stylesheets.js +43 -0
- package/dist/email/text.d.ts +7 -0
- package/dist/email/text.js +92 -0
- package/dist/emit.d.ts +13 -12
- package/dist/emit.js +63 -63
- package/dist/input-schema-BIfzI63B.d.ts +23 -0
- package/dist/input-schema.d.ts +2 -0
- package/dist/input-schema.js +170 -0
- package/dist/jsx-dev-runtime.d.ts +4 -3
- package/dist/jsx-dev-runtime.js +9 -1
- package/dist/{jsx-runtime-fSxcvlx4.d.ts → jsx-runtime-C5k_Zq0A.d.ts} +4 -4
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +20 -1
- package/dist/model.d.ts +1 -1
- package/dist/ops.d.ts +1 -1
- package/dist/ops.js +2 -2
- package/dist/{parser-yzjUOGhc.d.ts → parser-CGBS73Gz.d.ts} +12 -3
- package/dist/parser.d.ts +2 -2
- package/dist/parser.js +48 -9
- package/dist/predicate-CvkzT-a2.js +539 -0
- package/dist/{predicate-DTtVtLK7.d.ts → predicate-G2SeSvoN.d.ts} +9 -2
- package/dist/predicate.d.ts +2 -2
- package/dist/predicate.js +2 -417
- package/dist/presets/corpus.d.ts +2 -0
- package/dist/presets/corpus.js +186 -0
- package/dist/presets/public-gallery.d.ts +241 -459
- package/dist/presets/public-gallery.js +305 -5
- package/dist/presets/public-handoff.d.ts +75 -3
- package/dist/presets/public-handoff.js +220 -37
- package/dist/presets/public-preview-artifacts.js +20 -26
- package/dist/presets/public-preview.d.ts +40 -1152
- package/dist/presets/public-preview.js +388 -1152
- package/dist/presets/sources.d.ts +17 -4
- package/dist/presets/sources.js +1212 -43
- package/dist/presets.d.ts +2 -2
- package/dist/printer.d.ts +1 -1
- package/dist/render-Cddsy_XF.js +585 -0
- package/dist/render-template.d.ts +6 -3
- package/dist/render-template.js +4 -2
- package/dist/source-locations-BCJQnIfU.d.ts +89 -0
- package/dist/source-locations.d.ts +2 -0
- package/dist/source-locations.js +125 -0
- package/dist/stylesheets-oDRAnYA3.d.ts +94 -0
- package/dist/template-Dy5yOPyp.d.ts +60 -0
- package/dist/template.d.ts +2 -0
- package/dist/template.js +37 -0
- package/dist/tsx-source-instrumentation.d.ts +23 -0
- package/dist/tsx-source-instrumentation.js +97 -0
- package/dist/tsx-tracer.d.ts +116 -0
- package/dist/tsx-tracer.js +985 -0
- package/dist/{variables-3NGLpNTB.d.ts → variables-if1VrV34.d.ts} +2 -2
- package/dist/variables.d.ts +1 -1
- package/dist/variables.js +1 -1
- package/generated/sml-agent-contract.json +574 -782
- package/generated/sml-email-agent-contract.json +502 -677
- package/generated/sml-email-language-reference.md +94 -160
- package/generated/sml-language-reference.md +195 -220
- package/package.json +111 -8
|
@@ -1,12 +1,120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { compileSource } from "../compile.js";
|
|
4
|
-
import { RECOMMENDED_THEME, presetCatalog } from "../presets.js";
|
|
1
|
+
import { EMAIL_ELEMENTS } from "../email/elements.js";
|
|
2
|
+
import { presetCatalog } from "../presets.js";
|
|
5
3
|
import { publicTemplates, templateCollections } from "./public-gallery.js";
|
|
6
|
-
import { Result } from "effect";
|
|
4
|
+
import { Predicate, Result } from "effect";
|
|
5
|
+
import { parse } from "@babel/parser";
|
|
7
6
|
//#region presets/public-handoff.ts
|
|
8
7
|
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
8
|
const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
9
|
+
const MAX_HTTPS_ASSET_LENGTH = 2048;
|
|
10
|
+
const MAX_DATA_IMAGE_ASSET_LENGTH = 32768;
|
|
11
|
+
const DATA_IMAGE = /^data:image\/(?:avif|gif|jpeg|png|svg\+xml|webp)(?:;charset=[a-z0-9-]+)?(?:;base64)?,[^\r\n]+$/i;
|
|
12
|
+
const PUBLIC_TEMPLATE_ASSET_PATH = /^\/templates\/assets\/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp)$/;
|
|
13
|
+
const BLOCKED_PUBLIC_ASSET = /(?:unsplash|placehold|assets\.example)/i;
|
|
14
|
+
const isTemplateReferenceProvenanceSource = (value) => value === "samva-owned" || value === "sanitized-synthetic" || value === "reviewed-public";
|
|
15
|
+
/** Bound authored source before parsing or mounting it. */
|
|
16
|
+
const PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS = 512e3;
|
|
17
|
+
const PUBLIC_TEMPLATE_FONT_TOKENS = /* @__PURE__ */ new Set([
|
|
18
|
+
"sans",
|
|
19
|
+
"serif",
|
|
20
|
+
"mono"
|
|
21
|
+
]);
|
|
22
|
+
const FONT_WEIGHT_TOKENS = /* @__PURE__ */ new Set([
|
|
23
|
+
"thin",
|
|
24
|
+
"extralight",
|
|
25
|
+
"light",
|
|
26
|
+
"normal",
|
|
27
|
+
"medium",
|
|
28
|
+
"semibold",
|
|
29
|
+
"bold",
|
|
30
|
+
"extrabold",
|
|
31
|
+
"black"
|
|
32
|
+
]);
|
|
33
|
+
const parsePublicTemplateSource = (source) => {
|
|
34
|
+
if (source.length === 0 || source.length > 512e3) return void 0;
|
|
35
|
+
try {
|
|
36
|
+
const parsed = parse(source, {
|
|
37
|
+
sourceType: "module",
|
|
38
|
+
plugins: ["typescript", "jsx"]
|
|
39
|
+
});
|
|
40
|
+
return parsed.program.body.some((node) => node.type === "ExportDefaultDeclaration") ? parsed : void 0;
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const PUBLIC_COMPONENT_TAGS = /* @__PURE__ */ new Set([
|
|
46
|
+
...Object.keys(EMAIL_ELEMENTS),
|
|
47
|
+
"Email",
|
|
48
|
+
"Section",
|
|
49
|
+
"Columns",
|
|
50
|
+
"Column",
|
|
51
|
+
"Button",
|
|
52
|
+
"Spacer",
|
|
53
|
+
"Divider",
|
|
54
|
+
"Image",
|
|
55
|
+
"Link"
|
|
56
|
+
]);
|
|
57
|
+
/** Only generic element names and literal class colors survive deidentification. */
|
|
58
|
+
const publicTemplateAuthoredStructure = (source) => {
|
|
59
|
+
const parsed = parsePublicTemplateSource(source);
|
|
60
|
+
if (parsed === void 0) return void 0;
|
|
61
|
+
const nodes = [];
|
|
62
|
+
const counts = /* @__PURE__ */ new Map();
|
|
63
|
+
const palette = /* @__PURE__ */ new Set();
|
|
64
|
+
const siblings = /* @__PURE__ */ new Map();
|
|
65
|
+
const walk = (value, depth) => {
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
for (const item of value) walk(item, depth);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!Predicate.isObject(value)) return;
|
|
71
|
+
let childDepth = depth;
|
|
72
|
+
if (value.type === "JSXElement") {
|
|
73
|
+
const opening = value.openingElement;
|
|
74
|
+
if (!Predicate.isObject(opening)) return;
|
|
75
|
+
const name = opening.name;
|
|
76
|
+
const tag = Predicate.isObject(name) && typeof name.name === "string" && PUBLIC_COMPONENT_TAGS.has(name.name) ? name.name : "Component";
|
|
77
|
+
const childIndex = siblings.get(depth) ?? 0;
|
|
78
|
+
siblings.set(depth, childIndex + 1);
|
|
79
|
+
siblings.set(depth + 1, 0);
|
|
80
|
+
if (nodes.length < 200) nodes.push({
|
|
81
|
+
tag,
|
|
82
|
+
depth,
|
|
83
|
+
childIndex
|
|
84
|
+
});
|
|
85
|
+
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
86
|
+
if (Array.isArray(opening.attributes)) for (const attribute of opening.attributes) {
|
|
87
|
+
if (!Predicate.isObject(attribute) || !Predicate.isObject(attribute.name) || attribute.name.name !== "className") continue;
|
|
88
|
+
const literal = attribute.value;
|
|
89
|
+
if (!Predicate.isObject(literal) || literal.type !== "StringLiteral" || typeof literal.value !== "string") continue;
|
|
90
|
+
for (const match of literal.value.matchAll(/#([a-f0-9]{8}|[a-f0-9]{6}|[a-f0-9]{3})(?=[^a-f0-9]|$)/gi)) if (palette.size < 24) palette.add(`#${match[1].toLowerCase()}`);
|
|
91
|
+
}
|
|
92
|
+
childDepth += 1;
|
|
93
|
+
}
|
|
94
|
+
for (const [key, child] of Object.entries(value)) {
|
|
95
|
+
if ([
|
|
96
|
+
"loc",
|
|
97
|
+
"comments",
|
|
98
|
+
"leadingComments",
|
|
99
|
+
"trailingComments",
|
|
100
|
+
"innerComments",
|
|
101
|
+
"extra",
|
|
102
|
+
"tokens"
|
|
103
|
+
].includes(key)) continue;
|
|
104
|
+
walk(child, childDepth);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
walk(parsed.program, 0);
|
|
108
|
+
return {
|
|
109
|
+
basis: "authored-jsx",
|
|
110
|
+
nodes,
|
|
111
|
+
components: [...counts].sort(([a], [b]) => a.localeCompare(b)).slice(0, 64).map(([tag, count]) => ({
|
|
112
|
+
tag,
|
|
113
|
+
count
|
|
114
|
+
})),
|
|
115
|
+
palette: [...palette].sort()
|
|
116
|
+
};
|
|
117
|
+
};
|
|
10
118
|
const sortedUnique = (values) => [...new Set(values)].sort();
|
|
11
119
|
const sameStrings = (left, right) => JSON.stringify(sortedUnique(left)) === JSON.stringify(sortedUnique(right));
|
|
12
120
|
const nonEmpty = (value) => value.trim().length > 0;
|
|
@@ -16,6 +124,83 @@ const issue = (artifactId, code, message) => ({
|
|
|
16
124
|
message
|
|
17
125
|
});
|
|
18
126
|
/**
|
|
127
|
+
* Public imports may only refer to the default email Tailwind font-family tokens. A source may
|
|
128
|
+
* still use the supported `font-*` weight utilities; this check deliberately
|
|
129
|
+
* rejects a new family token instead of creating a corpus font-hosting path.
|
|
130
|
+
*/
|
|
131
|
+
const unsupportedPublicTemplateFontTokens = (source) => {
|
|
132
|
+
const unsupported = /* @__PURE__ */ new Set();
|
|
133
|
+
for (const match of source.matchAll(/\bclassName\s*=\s*(["'])(.*?)\1/gs)) for (const className of (match[2] ?? "").split(/\s+/)) {
|
|
134
|
+
if (!className.startsWith("font-")) continue;
|
|
135
|
+
const token = className.slice(5);
|
|
136
|
+
if (!PUBLIC_TEMPLATE_FONT_TOKENS.has(token) && !FONT_WEIGHT_TOKENS.has(token)) unsupported.add(token);
|
|
137
|
+
}
|
|
138
|
+
return [...unsupported].sort();
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Accept bounded HTTPS URLs, same-origin gallery files under `/templates/assets/`,
|
|
142
|
+
* or the preview's self-contained image data. Live Unsplash, placehold, and
|
|
143
|
+
* assets.example hosts are never safe for public sample data.
|
|
144
|
+
*/
|
|
145
|
+
const isSafePublicTemplateAsset = (value) => {
|
|
146
|
+
if (value.includes("..") || BLOCKED_PUBLIC_ASSET.test(value)) return false;
|
|
147
|
+
if (value.startsWith("data:")) return value.length <= MAX_DATA_IMAGE_ASSET_LENGTH && DATA_IMAGE.test(value);
|
|
148
|
+
if (PUBLIC_TEMPLATE_ASSET_PATH.test(value)) return value.length <= MAX_HTTPS_ASSET_LENGTH;
|
|
149
|
+
const url = URL.parse(value);
|
|
150
|
+
return value.length <= MAX_HTTPS_ASSET_LENGTH && url?.protocol === "https:" && url.hostname.length > 0 && url.username.length === 0 && url.password.length === 0;
|
|
151
|
+
};
|
|
152
|
+
/** Normalizes one approved handoff for gallery and future agent consumers. */
|
|
153
|
+
const toTemplateReferencePacket = (artifact) => ({
|
|
154
|
+
version: 1,
|
|
155
|
+
preset: artifact.preset,
|
|
156
|
+
gallery: {
|
|
157
|
+
...artifact.gallery,
|
|
158
|
+
path: `/templates/${artifact.gallery.slug}`
|
|
159
|
+
},
|
|
160
|
+
preview: artifact.preview,
|
|
161
|
+
publicMetadata: artifact.publicMetadata,
|
|
162
|
+
provenance: artifact.provenance
|
|
163
|
+
});
|
|
164
|
+
const jsonProjection = (kind, value) => ({
|
|
165
|
+
kind,
|
|
166
|
+
relativePath: `${kind}.json`,
|
|
167
|
+
contents: `${JSON.stringify(value, null, 2)}\n`
|
|
168
|
+
});
|
|
169
|
+
/**
|
|
170
|
+
* Emits the five destination projections as deterministic bytes. The importer
|
|
171
|
+
* owns no checked-in destination files; callers choose an explicit output dir.
|
|
172
|
+
*/
|
|
173
|
+
const emitTemplateReferencePacket = (packet) => [
|
|
174
|
+
{
|
|
175
|
+
kind: "source",
|
|
176
|
+
relativePath: "preset.tsx",
|
|
177
|
+
contents: packet.preset.source
|
|
178
|
+
},
|
|
179
|
+
jsonProjection("catalog", {
|
|
180
|
+
id: packet.preset.id,
|
|
181
|
+
channel: packet.preset.channel,
|
|
182
|
+
category: packet.preset.category,
|
|
183
|
+
name: packet.preset.name,
|
|
184
|
+
description: packet.preset.description,
|
|
185
|
+
variables: packet.preset.variables
|
|
186
|
+
}),
|
|
187
|
+
jsonProjection("gallery", {
|
|
188
|
+
presetId: packet.preset.id,
|
|
189
|
+
...packet.gallery,
|
|
190
|
+
provenance: packet.provenance
|
|
191
|
+
}),
|
|
192
|
+
jsonProjection("preview", packet.preview),
|
|
193
|
+
jsonProjection("editorial", {
|
|
194
|
+
cardTitle: packet.publicMetadata.cardTitle,
|
|
195
|
+
cardDescription: packet.publicMetadata.cardDescription,
|
|
196
|
+
guidance: packet.publicMetadata.guidance,
|
|
197
|
+
variables: packet.publicMetadata.variableDescriptions,
|
|
198
|
+
seo: packet.publicMetadata.seo
|
|
199
|
+
})
|
|
200
|
+
];
|
|
201
|
+
/**
|
|
202
|
+
* Validates metadata and TSX syntax, never executing the template. The Vite importer
|
|
203
|
+
* verifies schema, fixtures and rendered content before writing projections.
|
|
19
204
|
* Validates explicitly supplied, owner-approved artifacts without filesystem,
|
|
20
205
|
* network, persistence, or discovery. Successful output is a projection for
|
|
21
206
|
* the existing preset, public-gallery, preview, and editorial contracts; this
|
|
@@ -32,19 +217,17 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
32
217
|
for (const artifact of artifacts) {
|
|
33
218
|
const id = artifact.preset.id;
|
|
34
219
|
if (artifact.approval.state !== "approved" || !nonEmpty(artifact.approval.approvedBy) || !ISO_TIMESTAMP.test(artifact.approval.approvedAt)) issues.push(issue(id, "approval", "owner approval and an ISO-8601 approval timestamp are required"));
|
|
220
|
+
if (artifact.provenance === void 0 || !isTemplateReferenceProvenanceSource(artifact.provenance.source) || !nonEmpty(artifact.provenance.referenceId) || !ISO_TIMESTAMP.test(artifact.provenance.recordedAt)) issues.push(issue(id, "provenance", "provenance requires a supported source, identifier, and ISO-8601 timestamp"));
|
|
35
221
|
if (!SLUG.test(id) || !SLUG.test(artifact.gallery.slug)) issues.push(issue(id, "identity", "preset id and canonical slug must be lowercase kebab-case"));
|
|
36
222
|
if (existingIds.has(id) || suppliedIds.has(id)) issues.push(issue(id, "duplicate-id", `preset id "${id}" is already present`));
|
|
37
223
|
if (existingSlugs.has(artifact.gallery.slug) || suppliedSlugs.has(artifact.gallery.slug)) issues.push(issue(id, "duplicate-slug", `canonical slug "${artifact.gallery.slug}" is already present`));
|
|
38
224
|
suppliedIds.add(id);
|
|
39
225
|
suppliedSlugs.add(artifact.gallery.slug);
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
const sourceVariables = [...collectVariableReferences(parsed.success)];
|
|
46
|
-
if (!sameStrings(artifact.preset.variables, sourceVariables)) issues.push(issue(id, "variables", "declared variables must exactly match source references"));
|
|
47
|
-
}
|
|
226
|
+
if (parsePublicTemplateSource(artifact.preset.source) === void 0) issues.push(issue(id, "source", "source must be a bounded TSX module with a default export; execution is verified by the importer"));
|
|
227
|
+
if (artifact.preset.channel !== "email") issues.push(issue(id, "channel", "public template source must be an executable email"));
|
|
228
|
+
if (new Set(artifact.preset.variables).size !== artifact.preset.variables.length || artifact.preset.variables.some((value) => !nonEmpty(value))) issues.push(issue(id, "variables", "declared input fields must be non-empty and unique"));
|
|
229
|
+
const unsupportedFontTokens = unsupportedPublicTemplateFontTokens(artifact.preset.source);
|
|
230
|
+
if (unsupportedFontTokens.length > 0) issues.push(issue(id, "font-token", `unsupported public font token: ${unsupportedFontTokens.join(", ")}`));
|
|
48
231
|
const sampleVariables = Object.keys(artifact.preview.sampleData);
|
|
49
232
|
if (!sameStrings(sampleVariables, artifact.preset.variables)) issues.push(issue(id, "sample-data", "sample data must cover every declared variable exactly"));
|
|
50
233
|
if (Object.values(artifact.preview.sampleData).some((value) => !nonEmpty(value))) issues.push(issue(id, "sample-data", "sample data values must be non-empty"));
|
|
@@ -52,9 +235,8 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
52
235
|
if (new Set(assetVariables).size !== assetVariables.length) issues.push(issue(id, "assets", "asset variable references must be unique"));
|
|
53
236
|
for (const asset of artifact.preview.assets) {
|
|
54
237
|
const value = artifact.preview.sampleData[asset.variable];
|
|
55
|
-
const url = value === void 0 ? null : URL.parse(value);
|
|
56
238
|
if (!artifact.preset.variables.includes(asset.variable)) issues.push(issue(id, "assets", `asset "${asset.variable}" is not a declared preset variable`));
|
|
57
|
-
if (
|
|
239
|
+
if (value === void 0 || !isSafePublicTemplateAsset(value)) issues.push(issue(id, "assets", `asset "${asset.variable}" must reference a bounded HTTPS, /templates/assets, or data:image sample URL`));
|
|
58
240
|
}
|
|
59
241
|
const metadata = artifact.publicMetadata;
|
|
60
242
|
const metadataStrings = [
|
|
@@ -77,29 +259,30 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
77
259
|
for (const relatedId of artifact.gallery.relatedTemplates) if (relatedId === id || !allKnownIds.has(relatedId)) issues.push(issue(id, "reference", `related template "${relatedId}" is invalid`));
|
|
78
260
|
}
|
|
79
261
|
if (issues.length > 0) return Result.fail(issues);
|
|
80
|
-
return Result.succeed(artifacts.map((artifact) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
262
|
+
return Result.succeed(artifacts.map((artifact) => {
|
|
263
|
+
const reference = toTemplateReferencePacket(artifact);
|
|
264
|
+
return {
|
|
265
|
+
approval: {
|
|
266
|
+
...artifact.approval,
|
|
267
|
+
state: "approved"
|
|
268
|
+
},
|
|
269
|
+
preset: {
|
|
270
|
+
id: artifact.preset.id,
|
|
271
|
+
channel: artifact.preset.channel,
|
|
272
|
+
category: artifact.preset.category,
|
|
273
|
+
name: artifact.preset.name,
|
|
274
|
+
description: artifact.preset.description,
|
|
275
|
+
variables: artifact.preset.variables
|
|
276
|
+
},
|
|
277
|
+
source: artifact.preset.source,
|
|
278
|
+
gallery: reference.gallery,
|
|
279
|
+
preview: artifact.preview,
|
|
280
|
+
publicMetadata: artifact.publicMetadata,
|
|
281
|
+
reference
|
|
282
|
+
};
|
|
283
|
+
}));
|
|
101
284
|
};
|
|
102
285
|
//#endregion
|
|
103
|
-
export { validatePublicTemplateHandoff };
|
|
286
|
+
export { PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS, emitTemplateReferencePacket, isSafePublicTemplateAsset, publicTemplateAuthoredStructure, toTemplateReferencePacket, unsupportedPublicTemplateFontTokens, validatePublicTemplateHandoff };
|
|
104
287
|
|
|
105
288
|
//# sourceMappingURL=public-handoff.js.map
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { RECOMMENDED_THEME, presetCatalog } from "../presets.js";
|
|
1
|
+
import { presetCatalog } from "../presets.js";
|
|
2
|
+
import { registerPublicCorpusEntry } from "./corpus.js";
|
|
4
3
|
import { publicTemplates } from "./public-gallery.js";
|
|
4
|
+
import { presetEmailFixtures, presetModuleSource } from "./sources.js";
|
|
5
5
|
import { publicTemplatePreviews } from "./public-preview.js";
|
|
6
|
-
import { presetSource } from "./sources.js";
|
|
7
6
|
import { Result } from "effect";
|
|
8
7
|
//#region presets/public-preview-artifacts.ts
|
|
9
8
|
const renderFrames = [
|
|
@@ -46,29 +45,24 @@ const renderInputs = (slug, html) => renderFrames.flatMap((frame) => ["light", "
|
|
|
46
45
|
})));
|
|
47
46
|
/** Build one static preview artifact without loading the editor runtime or performing I/O. */
|
|
48
47
|
const buildPublicTemplatePreviewArtifact = (template) => {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
presetId: template.presetId,
|
|
54
|
-
message: "canonical preset source is missing"
|
|
48
|
+
const registration = registerPublicCorpusEntry({
|
|
49
|
+
id: template.presetId,
|
|
50
|
+
corpus: template.corpus,
|
|
51
|
+
facets: template.facets
|
|
55
52
|
});
|
|
56
|
-
|
|
57
|
-
if (Result.isFailure(compiled) || compiled.success.channel !== "email") return Result.fail({
|
|
53
|
+
if (Result.isFailure(registration)) return Result.fail({
|
|
58
54
|
presetId: template.presetId,
|
|
59
|
-
message:
|
|
55
|
+
message: `public corpus registration failed: ${registration.failure.message}`
|
|
60
56
|
});
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const rendered = interpolate(compiled.success.html, preview.sampleData, variableSchema);
|
|
67
|
-
if (rendered.missing.size > 0) return Result.fail({
|
|
57
|
+
const source = presetModuleSource(template.presetId);
|
|
58
|
+
const fixture = presetEmailFixtures[template.presetId]?.default;
|
|
59
|
+
const preset = presetCatalog.find((entry) => entry.id === template.presetId);
|
|
60
|
+
const preview = publicTemplatePreviews[template.presetId];
|
|
61
|
+
if (source === void 0 || preset === void 0 || fixture === void 0) return Result.fail({
|
|
68
62
|
presetId: template.presetId,
|
|
69
|
-
message:
|
|
63
|
+
message: "canonical preset source is missing"
|
|
70
64
|
});
|
|
71
|
-
const sampleData =
|
|
65
|
+
const sampleData = fixture.input;
|
|
72
66
|
const assets = [];
|
|
73
67
|
for (const asset of preview.assets) {
|
|
74
68
|
const value = sampleData[asset.variable];
|
|
@@ -81,13 +75,13 @@ const buildPublicTemplatePreviewArtifact = (template) => {
|
|
|
81
75
|
return Result.succeed({
|
|
82
76
|
presetId: template.presetId,
|
|
83
77
|
slug: template.slug,
|
|
84
|
-
subject:
|
|
85
|
-
preheader:
|
|
78
|
+
subject: fixture.subject,
|
|
79
|
+
preheader: fixture.preheader,
|
|
86
80
|
quality: preview.quality,
|
|
87
81
|
source,
|
|
88
|
-
html:
|
|
82
|
+
html: fixture.html,
|
|
89
83
|
assets,
|
|
90
|
-
renders: renderInputs(template.slug,
|
|
84
|
+
renders: renderInputs(template.slug, fixture.html)
|
|
91
85
|
});
|
|
92
86
|
};
|
|
93
87
|
/** Build only the requested canonical subset, preserving manifest order and aggregated failures. */
|