@samva/markup 0.2.0 → 0.4.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/dist/catalog-Ck1tAGwO.d.ts +356 -0
- package/dist/compiler.d.ts +7 -5
- package/dist/compiler.js +7 -6
- package/dist/{corpus-Dz5irJ9x.d.ts → corpus-U2EeALvS.d.ts} +76 -3
- package/dist/{stylesheets-oDRAnYA3.d.ts → css-ar2lU3NC.d.ts} +2 -12
- package/dist/email/components.d.ts +8 -2
- package/dist/email/components.js +8 -5
- package/dist/email/render.d.ts +8 -1
- package/dist/email/render.js +2 -2
- package/dist/email/stylesheets.d.ts +15 -2
- package/dist/email/stylesheets.js +3 -1
- package/dist/email/text.js +9 -1
- package/dist/presets/category-mapping.d.ts +1 -1
- package/dist/presets/corpus.d.ts +2 -2
- package/dist/presets/corpus.js +55 -4
- package/dist/presets/public-gallery.d.ts +123 -129
- package/dist/presets/public-gallery.js +260 -215
- package/dist/presets/public-handoff.d.ts +31 -7
- package/dist/presets/public-handoff.js +107 -35
- package/dist/presets/public-preview-artifacts.d.ts +1 -1
- package/dist/presets/public-preview-artifacts.js +6 -6
- package/dist/presets/public-preview.d.ts +11 -6
- package/dist/presets/public-preview.js +48 -359
- package/dist/presets/showcase.d.ts +2 -0
- package/dist/presets/showcase.js +627 -0
- package/dist/presets/sources.d.ts +21 -3
- package/dist/presets/sources.js +6008 -1148
- package/dist/presets.d.ts +2 -350
- package/dist/presets.js +395 -765
- package/dist/{render-Cddsy_XF.js → render-ykQhtMs_.js} +13 -6
- package/dist/showcase-C_xgl6hz.d.ts +456 -0
- package/package.json +5 -1
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { d as Channel } from "../ast-63I-VLQx.js";
|
|
2
|
-
import { PresetCategory } from "../
|
|
2
|
+
import { r as PresetCategory } from "../catalog-Ck1tAGwO.js";
|
|
3
3
|
import { TemplateCollectionId, TemplateUseCase } from "./public-gallery.js";
|
|
4
4
|
import { PublicTemplatePreview } from "./public-preview.js";
|
|
5
|
+
import { PresetProject } from "./sources.js";
|
|
5
6
|
import { Result } from "effect";
|
|
6
7
|
//#region presets/public-handoff.d.ts
|
|
8
|
+
/** The brand a clean-room recreation names, and what its email does. */
|
|
9
|
+
interface TemplateBrandRecreation {
|
|
10
|
+
readonly brand: string;
|
|
11
|
+
readonly subject: string;
|
|
12
|
+
}
|
|
7
13
|
/** Where an approved public artifact came from. */
|
|
8
14
|
interface TemplateReferenceProvenance {
|
|
9
|
-
readonly source: "samva-owned" | "sanitized-synthetic" | "reviewed-public";
|
|
15
|
+
readonly source: "samva-owned" | "sanitized-synthetic" | "reviewed-public" | "brand-recreation";
|
|
16
|
+
readonly brandRecreation?: TemplateBrandRecreation;
|
|
10
17
|
/** Stable identifier in the producing system; never a third-party URL. */
|
|
11
18
|
readonly referenceId: string;
|
|
12
19
|
/** Stable ISO-8601 timestamp supplied by the producer. */
|
|
@@ -27,6 +34,7 @@ interface ApprovedPublicTemplateArtifact {
|
|
|
27
34
|
readonly description: string;
|
|
28
35
|
readonly variables: ReadonlyArray<string>;
|
|
29
36
|
readonly source: string;
|
|
37
|
+
readonly project?: PresetProject;
|
|
30
38
|
};
|
|
31
39
|
readonly gallery: {
|
|
32
40
|
readonly slug: string;
|
|
@@ -62,8 +70,9 @@ interface TemplateReferencePacket {
|
|
|
62
70
|
readonly provenance: TemplateReferenceProvenance;
|
|
63
71
|
}
|
|
64
72
|
interface ValidatedPublicTemplateArtifact {
|
|
65
|
-
readonly preset: Omit<ApprovedPublicTemplateArtifact["preset"], "source">;
|
|
73
|
+
readonly preset: Omit<ApprovedPublicTemplateArtifact["preset"], "source" | "project">;
|
|
66
74
|
readonly source: string;
|
|
75
|
+
readonly project?: PresetProject;
|
|
67
76
|
readonly gallery: ApprovedPublicTemplateArtifact["gallery"] & {
|
|
68
77
|
readonly path: `/templates/${string}`;
|
|
69
78
|
};
|
|
@@ -82,6 +91,16 @@ interface PublicHandoffIssue {
|
|
|
82
91
|
}
|
|
83
92
|
/** Bound authored source before parsing or mounting it. */
|
|
84
93
|
declare const PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS = 512000;
|
|
94
|
+
/** Bounds apply to decoded bytes, before evaluation or disk emission. */
|
|
95
|
+
declare const PUBLIC_TEMPLATE_PROJECT_MAX_BYTES: number;
|
|
96
|
+
declare const PUBLIC_TEMPLATE_PROJECT_FILE_MAX_BYTES: number;
|
|
97
|
+
declare const PUBLIC_TEMPLATE_PROJECT_MAX_FILES = 64;
|
|
98
|
+
declare const isSafePublicTemplateProjectPath: (path: string) => boolean;
|
|
99
|
+
/** Reject traversal, collisions, malformed base64 and oversized authored projects. */
|
|
100
|
+
declare const isSafePublicTemplateProject: (project: PresetProject) => boolean;
|
|
101
|
+
/** Digest the complete authored graph independently of caller file ordering. */
|
|
102
|
+
declare const publicTemplateProjectDigest: (project: Pick<PresetProject, "entryPath" | "files">) => Promise<string>;
|
|
103
|
+
declare const isValidTemplateBrandRecreation: (brandRecreation: TemplateBrandRecreation | undefined, project: PresetProject | undefined) => boolean;
|
|
85
104
|
/** Static syntax inventory; loops, branches and component expansion are not evaluated. */
|
|
86
105
|
interface PublicTemplateAuthoredStructure {
|
|
87
106
|
readonly basis: "authored-jsx";
|
|
@@ -117,12 +136,17 @@ type TemplateReferenceProjection = {
|
|
|
117
136
|
readonly relativePath: "preset.tsx";
|
|
118
137
|
readonly contents: string;
|
|
119
138
|
} | {
|
|
120
|
-
readonly kind: "
|
|
121
|
-
readonly relativePath:
|
|
139
|
+
readonly kind: "project-file";
|
|
140
|
+
readonly relativePath: `project/${string}`;
|
|
141
|
+
readonly contents: string;
|
|
142
|
+
readonly encoding: "utf8" | "base64";
|
|
143
|
+
} | {
|
|
144
|
+
readonly kind: "catalog" | "gallery" | "preview" | "editorial" | "project";
|
|
145
|
+
readonly relativePath: `${"catalog" | "gallery" | "preview" | "editorial" | "project"}.json`;
|
|
122
146
|
readonly contents: string;
|
|
123
147
|
};
|
|
124
148
|
/**
|
|
125
|
-
* Emits the
|
|
149
|
+
* Emits legacy projections plus the complete project as deterministic bytes. The importer
|
|
126
150
|
* owns no checked-in destination files; callers choose an explicit output dir.
|
|
127
151
|
*/
|
|
128
152
|
declare const emitTemplateReferencePacket: (packet: TemplateReferencePacket) => ReadonlyArray<TemplateReferenceProjection>;
|
|
@@ -136,5 +160,5 @@ declare const emitTemplateReferencePacket: (packet: TemplateReferencePacket) =>
|
|
|
136
160
|
*/
|
|
137
161
|
declare const validatePublicTemplateHandoff: (artifacts: ReadonlyArray<ApprovedPublicTemplateArtifact>) => Result.Result<ReadonlyArray<ValidatedPublicTemplateArtifact>, ReadonlyArray<PublicHandoffIssue>>;
|
|
138
162
|
//#endregion
|
|
139
|
-
export { ApprovedPublicTemplateArtifact, PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS, PublicHandoffIssue, PublicHandoffIssueCode, PublicTemplateAuthoredStructure, TemplateReferencePacket, TemplateReferenceProjection, TemplateReferenceProvenance, ValidatedPublicTemplateArtifact, emitTemplateReferencePacket, isSafePublicTemplateAsset, publicTemplateAuthoredStructure, toTemplateReferencePacket, unsupportedPublicTemplateFontTokens, validatePublicTemplateHandoff };
|
|
163
|
+
export { ApprovedPublicTemplateArtifact, PUBLIC_TEMPLATE_PROJECT_FILE_MAX_BYTES, PUBLIC_TEMPLATE_PROJECT_MAX_BYTES, PUBLIC_TEMPLATE_PROJECT_MAX_FILES, PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS, PublicHandoffIssue, PublicHandoffIssueCode, PublicTemplateAuthoredStructure, TemplateBrandRecreation, TemplateReferencePacket, TemplateReferenceProjection, TemplateReferenceProvenance, ValidatedPublicTemplateArtifact, emitTemplateReferencePacket, isSafePublicTemplateAsset, isSafePublicTemplateProject, isSafePublicTemplateProjectPath, isValidTemplateBrandRecreation, publicTemplateAuthoredStructure, publicTemplateProjectDigest, toTemplateReferencePacket, unsupportedPublicTemplateFontTokens, validatePublicTemplateHandoff };
|
|
140
164
|
//# sourceMappingURL=public-handoff.d.ts.map
|
|
@@ -11,9 +11,62 @@ const MAX_DATA_IMAGE_ASSET_LENGTH = 32768;
|
|
|
11
11
|
const DATA_IMAGE = /^data:image\/(?:avif|gif|jpeg|png|svg\+xml|webp)(?:;charset=[a-z0-9-]+)?(?:;base64)?,[^\r\n]+$/i;
|
|
12
12
|
const PUBLIC_TEMPLATE_ASSET_PATH = /^\/templates\/assets\/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp)$/;
|
|
13
13
|
const BLOCKED_PUBLIC_ASSET = /(?:unsplash|placehold|assets\.example)/i;
|
|
14
|
-
|
|
14
|
+
/** A sample value is non-empty when every string in it is, and no list or object is empty. */
|
|
15
|
+
const nonEmptySample = (value) => {
|
|
16
|
+
if (typeof value === "string") return nonEmpty(value);
|
|
17
|
+
if (Array.isArray(value)) return value.length > 0 && value.every(nonEmptySample);
|
|
18
|
+
if (Predicate.isObject(value)) {
|
|
19
|
+
const values = Object.values(value);
|
|
20
|
+
return values.length > 0 && values.every(nonEmptySample);
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
|
+
const isTemplateReferenceProvenanceSource = (value) => value === "samva-owned" || value === "sanitized-synthetic" || value === "reviewed-public" || value === "brand-recreation";
|
|
15
25
|
/** Bound authored source before parsing or mounting it. */
|
|
16
26
|
const PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS = 512e3;
|
|
27
|
+
/** Bounds apply to decoded bytes, before evaluation or disk emission. */
|
|
28
|
+
const PUBLIC_TEMPLATE_PROJECT_MAX_BYTES = 8388608;
|
|
29
|
+
const PUBLIC_TEMPLATE_PROJECT_FILE_MAX_BYTES = 2097152;
|
|
30
|
+
const PUBLIC_TEMPLATE_PROJECT_MAX_FILES = 64;
|
|
31
|
+
const isSafePublicTemplateProjectPath = (path) => path.length <= 240 && path.split("/").every((part) => /^[A-Za-z0-9_][A-Za-z0-9._-]*$/.test(part) && part !== "." && part !== ".." && part !== "node_modules");
|
|
32
|
+
/** Reject traversal, collisions, malformed base64 and oversized authored projects. */
|
|
33
|
+
const isSafePublicTemplateProject = (project) => {
|
|
34
|
+
if (!isSafePublicTemplateProjectPath(project.entryPath) || !project.entryPath.endsWith(".tsx") || !/^[a-f0-9]{64}$/.test(project.digest) || project.files.length === 0 || project.files.length > 64) return false;
|
|
35
|
+
const paths = /* @__PURE__ */ new Set();
|
|
36
|
+
let total = 0;
|
|
37
|
+
for (const file of project.files) {
|
|
38
|
+
if (!isSafePublicTemplateProjectPath(file.path) || paths.has(file.path.toLowerCase())) return false;
|
|
39
|
+
paths.add(file.path.toLowerCase());
|
|
40
|
+
let bytes;
|
|
41
|
+
if (file.encoding === "utf8") {
|
|
42
|
+
if (file.content.length > 512e3) return false;
|
|
43
|
+
bytes = new TextEncoder().encode(file.content).length;
|
|
44
|
+
} else if (file.encoding === "base64") {
|
|
45
|
+
if (file.content.length > Math.ceil(2097152 / 3) * 4 || file.content.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(file.content)) return false;
|
|
46
|
+
if (btoa(atob(file.content)) !== file.content) return false;
|
|
47
|
+
bytes = file.content.length / 4 * 3 - (file.content.endsWith("==") ? 2 : file.content.endsWith("=") ? 1 : 0);
|
|
48
|
+
} else return false;
|
|
49
|
+
if (bytes > 2097152) return false;
|
|
50
|
+
total += bytes;
|
|
51
|
+
if (total > 8388608) return false;
|
|
52
|
+
}
|
|
53
|
+
for (const path of paths) if (path.split("/").slice(0, -1).some((_, index, parts) => paths.has(parts.slice(0, index + 1).join("/")))) return false;
|
|
54
|
+
return project.files.some((file) => file.path === project.entryPath && file.encoding === "utf8");
|
|
55
|
+
};
|
|
56
|
+
/** Digest the complete authored graph independently of caller file ordering. */
|
|
57
|
+
const publicTemplateProjectDigest = async (project) => {
|
|
58
|
+
const serialized = JSON.stringify({
|
|
59
|
+
entryPath: project.entryPath,
|
|
60
|
+
files: [...project.files].sort((a, b) => a.path.localeCompare(b.path)).map(({ path, content, encoding }) => ({
|
|
61
|
+
path,
|
|
62
|
+
content,
|
|
63
|
+
encoding
|
|
64
|
+
}))
|
|
65
|
+
});
|
|
66
|
+
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialized)));
|
|
67
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
68
|
+
};
|
|
69
|
+
const isValidTemplateBrandRecreation = (brandRecreation, project) => brandRecreation !== void 0 && project !== void 0 && brandRecreation.brand.trim().length > 0 && brandRecreation.subject.trim().length > 0;
|
|
17
70
|
const PUBLIC_TEMPLATE_FONT_TOKENS = /* @__PURE__ */ new Set([
|
|
18
71
|
"sans",
|
|
19
72
|
"serif",
|
|
@@ -167,37 +220,51 @@ const jsonProjection = (kind, value) => ({
|
|
|
167
220
|
contents: `${JSON.stringify(value, null, 2)}\n`
|
|
168
221
|
});
|
|
169
222
|
/**
|
|
170
|
-
* Emits the
|
|
223
|
+
* Emits legacy projections plus the complete project as deterministic bytes. The importer
|
|
171
224
|
* owns no checked-in destination files; callers choose an explicit output dir.
|
|
172
225
|
*/
|
|
173
|
-
const emitTemplateReferencePacket = (packet) =>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
226
|
+
const emitTemplateReferencePacket = (packet) => {
|
|
227
|
+
const project = packet.preset.project;
|
|
228
|
+
if (project !== void 0 && (!isSafePublicTemplateProject(project) || project.files.find((file) => file.path === project.entryPath)?.content !== packet.preset.source)) throw new Error("Invalid public template project");
|
|
229
|
+
if (packet.provenance.source === "brand-recreation" && !isValidTemplateBrandRecreation(packet.provenance.brandRecreation, project)) throw new Error("Invalid public template brand recreation attribution");
|
|
230
|
+
return [
|
|
231
|
+
...project === void 0 ? [] : [jsonProjection("project", {
|
|
232
|
+
...project,
|
|
233
|
+
files: [...project.files].sort((a, b) => a.path.localeCompare(b.path))
|
|
234
|
+
}), ...[...project.files].sort((a, b) => a.path.localeCompare(b.path)).map((file) => ({
|
|
235
|
+
kind: "project-file",
|
|
236
|
+
relativePath: `project/${file.path}`,
|
|
237
|
+
contents: file.content,
|
|
238
|
+
encoding: file.encoding
|
|
239
|
+
}))],
|
|
240
|
+
{
|
|
241
|
+
kind: "source",
|
|
242
|
+
relativePath: "preset.tsx",
|
|
243
|
+
contents: packet.preset.source
|
|
244
|
+
},
|
|
245
|
+
jsonProjection("catalog", {
|
|
246
|
+
id: packet.preset.id,
|
|
247
|
+
channel: packet.preset.channel,
|
|
248
|
+
category: packet.preset.category,
|
|
249
|
+
name: packet.preset.name,
|
|
250
|
+
description: packet.preset.description,
|
|
251
|
+
variables: packet.preset.variables
|
|
252
|
+
}),
|
|
253
|
+
jsonProjection("gallery", {
|
|
254
|
+
presetId: packet.preset.id,
|
|
255
|
+
...packet.gallery,
|
|
256
|
+
provenance: packet.provenance
|
|
257
|
+
}),
|
|
258
|
+
jsonProjection("preview", packet.preview),
|
|
259
|
+
jsonProjection("editorial", {
|
|
260
|
+
cardTitle: packet.publicMetadata.cardTitle,
|
|
261
|
+
cardDescription: packet.publicMetadata.cardDescription,
|
|
262
|
+
guidance: packet.publicMetadata.guidance,
|
|
263
|
+
variables: packet.publicMetadata.variableDescriptions,
|
|
264
|
+
seo: packet.publicMetadata.seo
|
|
265
|
+
})
|
|
266
|
+
];
|
|
267
|
+
};
|
|
201
268
|
/**
|
|
202
269
|
* Validates metadata and TSX syntax, never executing the template. The Vite importer
|
|
203
270
|
* verifies schema, fixtures and rendered content before writing projections.
|
|
@@ -223,20 +290,24 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
223
290
|
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`));
|
|
224
291
|
suppliedIds.add(id);
|
|
225
292
|
suppliedSlugs.add(artifact.gallery.slug);
|
|
293
|
+
const project = artifact.preset.project;
|
|
294
|
+
if (project !== void 0 && (!isSafePublicTemplateProject(project) || project.files.find((file) => file.path === project.entryPath)?.content !== artifact.preset.source)) issues.push(issue(id, "source", "project must contain safe bounded unique files and the matching UTF-8 entry source"));
|
|
295
|
+
if (artifact.provenance?.source === "brand-recreation" && !isValidTemplateBrandRecreation(artifact.provenance.brandRecreation, project)) issues.push(issue(id, "provenance", "brand recreations require a complete project, a referenced brand, and its email subject"));
|
|
226
296
|
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
297
|
if (artifact.preset.channel !== "email") issues.push(issue(id, "channel", "public template source must be an executable email"));
|
|
228
298
|
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
299
|
const unsupportedFontTokens = unsupportedPublicTemplateFontTokens(artifact.preset.source);
|
|
230
300
|
if (unsupportedFontTokens.length > 0) issues.push(issue(id, "font-token", `unsupported public font token: ${unsupportedFontTokens.join(", ")}`));
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
if (
|
|
301
|
+
const declaredVariables = new Set(artifact.preset.variables);
|
|
302
|
+
const undeclaredSample = Object.keys(artifact.preview.sampleData).filter((variable) => !declaredVariables.has(variable));
|
|
303
|
+
if (undeclaredSample.length > 0) issues.push(issue(id, "sample-data", `sample data supplies undeclared variables: ${sortedUnique(undeclaredSample).join(", ")}`));
|
|
304
|
+
if (Object.values(artifact.preview.sampleData).some((value) => !nonEmptySample(value))) issues.push(issue(id, "sample-data", "sample data values must be non-empty"));
|
|
234
305
|
const assetVariables = artifact.preview.assets.map((asset) => asset.variable);
|
|
235
306
|
if (new Set(assetVariables).size !== assetVariables.length) issues.push(issue(id, "assets", "asset variable references must be unique"));
|
|
236
307
|
for (const asset of artifact.preview.assets) {
|
|
237
308
|
const value = artifact.preview.sampleData[asset.variable];
|
|
238
309
|
if (!artifact.preset.variables.includes(asset.variable)) issues.push(issue(id, "assets", `asset "${asset.variable}" is not a declared preset variable`));
|
|
239
|
-
if (value
|
|
310
|
+
if (typeof value !== "string" || !isSafePublicTemplateAsset(value)) issues.push(issue(id, "assets", `asset "${asset.variable}" must reference a bounded HTTPS, /templates/assets, or data:image sample URL`));
|
|
240
311
|
}
|
|
241
312
|
const metadata = artifact.publicMetadata;
|
|
242
313
|
const metadataStrings = [
|
|
@@ -275,6 +346,7 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
275
346
|
variables: artifact.preset.variables
|
|
276
347
|
},
|
|
277
348
|
source: artifact.preset.source,
|
|
349
|
+
...artifact.preset.project === void 0 ? {} : { project: artifact.preset.project },
|
|
278
350
|
gallery: reference.gallery,
|
|
279
351
|
preview: artifact.preview,
|
|
280
352
|
publicMetadata: artifact.publicMetadata,
|
|
@@ -283,6 +355,6 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
283
355
|
}));
|
|
284
356
|
};
|
|
285
357
|
//#endregion
|
|
286
|
-
export { PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS, emitTemplateReferencePacket, isSafePublicTemplateAsset, publicTemplateAuthoredStructure, toTemplateReferencePacket, unsupportedPublicTemplateFontTokens, validatePublicTemplateHandoff };
|
|
358
|
+
export { PUBLIC_TEMPLATE_PROJECT_FILE_MAX_BYTES, PUBLIC_TEMPLATE_PROJECT_MAX_BYTES, PUBLIC_TEMPLATE_PROJECT_MAX_FILES, PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS, emitTemplateReferencePacket, isSafePublicTemplateAsset, isSafePublicTemplateProject, isSafePublicTemplateProjectPath, isValidTemplateBrandRecreation, publicTemplateAuthoredStructure, publicTemplateProjectDigest, toTemplateReferencePacket, unsupportedPublicTemplateFontTokens, validatePublicTemplateHandoff };
|
|
287
359
|
|
|
288
360
|
//# sourceMappingURL=public-handoff.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { presetCatalog } from "../presets.js";
|
|
2
2
|
import { registerPublicCorpusEntry } from "./corpus.js";
|
|
3
3
|
import { publicTemplates } from "./public-gallery.js";
|
|
4
|
-
import { presetEmailFixtures, presetModuleSource } from "./sources.js";
|
|
4
|
+
import { presetEmailAssets, presetEmailFixtures, presetModuleSource } from "./sources.js";
|
|
5
5
|
import { publicTemplatePreviews } from "./public-preview.js";
|
|
6
6
|
import { Result } from "effect";
|
|
7
7
|
//#region presets/public-preview-artifacts.ts
|
|
@@ -63,14 +63,14 @@ const buildPublicTemplatePreviewArtifact = (template) => {
|
|
|
63
63
|
message: "canonical preset source is missing"
|
|
64
64
|
});
|
|
65
65
|
const sampleData = fixture.input;
|
|
66
|
-
const assets = [];
|
|
66
|
+
const assets = new Set(presetEmailAssets[template.presetId]);
|
|
67
67
|
for (const asset of preview.assets) {
|
|
68
68
|
const value = sampleData[asset.variable];
|
|
69
|
-
if (value
|
|
69
|
+
if (typeof value !== "string") return Result.fail({
|
|
70
70
|
presetId: template.presetId,
|
|
71
|
-
message: `sample data is missing asset: ${asset.variable}`
|
|
71
|
+
message: `sample data is missing a string asset: ${asset.variable}`
|
|
72
72
|
});
|
|
73
|
-
assets.
|
|
73
|
+
assets.add(value);
|
|
74
74
|
}
|
|
75
75
|
return Result.succeed({
|
|
76
76
|
presetId: template.presetId,
|
|
@@ -80,7 +80,7 @@ const buildPublicTemplatePreviewArtifact = (template) => {
|
|
|
80
80
|
quality: preview.quality,
|
|
81
81
|
source,
|
|
82
82
|
html: fixture.html,
|
|
83
|
-
assets,
|
|
83
|
+
assets: [...assets],
|
|
84
84
|
renders: renderInputs(template.slug, fixture.html)
|
|
85
85
|
});
|
|
86
86
|
};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
//#region presets/public-preview.d.ts
|
|
2
2
|
type PreviewQuality = "ready" | "asset-free";
|
|
3
|
+
/** A declared fixture value: JSON, so a template can take a list of stories as well as a string. */
|
|
4
|
+
type PreviewSampleValue = string | number | boolean | null | ReadonlyArray<PreviewSampleValue> | {
|
|
5
|
+
readonly [key: string]: PreviewSampleValue;
|
|
6
|
+
};
|
|
3
7
|
interface PublicTemplatePreview {
|
|
4
8
|
readonly subject: string;
|
|
5
9
|
readonly preheader: string;
|
|
6
10
|
readonly quality: PreviewQuality;
|
|
7
|
-
readonly sampleData: Readonly<Record<string,
|
|
11
|
+
readonly sampleData: Readonly<Record<string, PreviewSampleValue>>;
|
|
8
12
|
readonly assets: ReadonlyArray<{
|
|
9
13
|
readonly variable: string;
|
|
10
14
|
readonly kind: "image" | "logo";
|
|
@@ -12,19 +16,21 @@ interface PublicTemplatePreview {
|
|
|
12
16
|
}
|
|
13
17
|
/** Gallery metadata augments the actual default fixture; it does not supply render input. */
|
|
14
18
|
declare const publicTemplatePreviews: {
|
|
19
|
+
readonly "linear-login-code": PublicTemplatePreview;
|
|
20
|
+
readonly "product-newsletter": PublicTemplatePreview;
|
|
21
|
+
readonly "order-receipt": PublicTemplatePreview;
|
|
22
|
+
readonly "vercel-invitation": PublicTemplatePreview;
|
|
23
|
+
readonly "product-launch-promo": PublicTemplatePreview;
|
|
15
24
|
readonly welcome: PublicTemplatePreview;
|
|
16
25
|
readonly "email-verification": PublicTemplatePreview;
|
|
17
26
|
readonly otp: PublicTemplatePreview;
|
|
18
27
|
readonly "password-reset": PublicTemplatePreview;
|
|
19
|
-
readonly receipt: PublicTemplatePreview;
|
|
20
28
|
readonly "payment-failed": PublicTemplatePreview;
|
|
21
29
|
readonly "shipping-update": PublicTemplatePreview;
|
|
22
30
|
readonly notification: PublicTemplatePreview;
|
|
23
31
|
readonly "team-invite": PublicTemplatePreview;
|
|
24
32
|
readonly "product-digest": PublicTemplatePreview;
|
|
25
|
-
readonly newsletter: PublicTemplatePreview;
|
|
26
33
|
readonly announcement: PublicTemplatePreview;
|
|
27
|
-
readonly promo: PublicTemplatePreview;
|
|
28
34
|
readonly "event-invite": PublicTemplatePreview;
|
|
29
35
|
readonly "membership-promo": PublicTemplatePreview;
|
|
30
36
|
readonly "onboarding-step": PublicTemplatePreview;
|
|
@@ -36,7 +42,6 @@ declare const publicTemplatePreviews: {
|
|
|
36
42
|
readonly "catalog-sale-newsletter": PublicTemplatePreview;
|
|
37
43
|
readonly "limited-time-savings": PublicTemplatePreview;
|
|
38
44
|
readonly "rewards-milestone": PublicTemplatePreview;
|
|
39
|
-
readonly "order-delivered": PublicTemplatePreview;
|
|
40
45
|
readonly "travel-rewards-promo": PublicTemplatePreview;
|
|
41
46
|
readonly "events-roundup": PublicTemplatePreview;
|
|
42
47
|
readonly "product-onboarding-tour": PublicTemplatePreview;
|
|
@@ -52,5 +57,5 @@ declare const publicTemplatePreviews: {
|
|
|
52
57
|
readonly "season-clearance-promo": PublicTemplatePreview;
|
|
53
58
|
};
|
|
54
59
|
//#endregion
|
|
55
|
-
export { PreviewQuality, PublicTemplatePreview, publicTemplatePreviews };
|
|
60
|
+
export { PreviewQuality, PreviewSampleValue, PublicTemplatePreview, publicTemplatePreviews };
|
|
56
61
|
//# sourceMappingURL=public-preview.d.ts.map
|