@samva/markup 0.8.0 → 0.9.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 +90 -37
- package/README.md +188 -52
- package/dist/assets-C5duwTya.d.ts +40 -0
- package/dist/{brand-DcoVkh_S.d.ts → brand-B-0bN_Ir.d.ts} +2 -2
- package/dist/{render-vAv_4r9v.js → cascade-t6kxlgEI.js} +93 -280
- package/dist/channel-edit.d.ts +121 -0
- package/dist/channel-edit.js +609 -0
- package/dist/channel-specs-B6M_HlaV.js +29 -0
- package/dist/channel-specs-C2feJwuP.d.ts +13 -0
- package/dist/compatibility-DbISoFIq.js +589 -0
- package/dist/compiler.d.ts +10 -257
- package/dist/compiler.js +10 -730
- package/dist/{css-CS2jLID6.d.ts → css-BVQKICKC.d.ts} +3 -3
- package/dist/diagnostic-codes.d.ts +403 -0
- package/dist/diagnostic-codes.js +399 -0
- package/dist/{diagnostics-BMYtBCQk.d.ts → diagnostics-B-Gym34R.d.ts} +8 -4
- package/dist/diagnostics.d.ts +31 -2
- package/dist/diagnostics.js +1 -19
- package/dist/{elements-BzjjnoCS.d.ts → elements-cqldpaJt.d.ts} +3 -3
- package/dist/email/assets.d.ts +2 -40
- package/dist/email/brand-footer.d.ts +2 -2
- package/dist/email/brand-footer.js +4 -3
- package/dist/email/brand.d.ts +1 -1
- package/dist/email/brand.js +1 -1
- package/dist/email/components.d.ts +21 -10
- package/dist/email/components.js +46 -11
- package/dist/email/diagnostics.d.ts +2 -2
- package/dist/email/diagnostics.js +2 -1
- package/dist/email/elements.d.ts +1 -1
- package/dist/email/html.d.ts +2 -2
- package/dist/email/html.js +45 -2
- package/dist/email/jsx-dev-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/render.d.ts +8 -18
- package/dist/email/render.js +163 -3
- package/dist/email/text.d.ts +1 -1
- package/dist/fmt.d.ts +32 -0
- package/dist/fmt.js +174 -0
- package/dist/{html-Bl9BAdqc.d.ts → html-BJepOwZ4.d.ts} +19 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/ir-aaqNE987.d.ts +252 -0
- package/dist/ir.d.ts +2 -0
- package/dist/ir.js +7 -0
- package/dist/preview-Ekdg1dXG.d.ts +100 -0
- package/dist/preview.d.ts +2 -0
- package/dist/preview.js +160 -0
- package/dist/profile-BH-OtdDA.js +3670 -0
- package/dist/render-ir.d.ts +19 -0
- package/dist/render-ir.js +826 -0
- package/dist/sms-segments.d.ts +13 -2
- package/dist/sms.d.ts +16 -0
- package/dist/sms.js +12 -0
- package/dist/{email/source-edit.d.ts → source-edit.d.ts} +55 -6
- package/dist/source-edit.js +343 -0
- package/dist/source-locations-C8wCsiAy.d.ts +23 -0
- package/dist/source-locations.d.ts +2 -2
- package/dist/source-locations.js +3 -89
- package/dist/static.d.ts +57 -0
- package/dist/static.js +69 -0
- package/dist/tailwind-CMYpZf1v.js +614 -0
- package/dist/template-_QE_rQND.d.ts +65 -0
- package/dist/template.d.ts +2 -2
- package/dist/template.js +15 -10
- package/dist/theme-css.d.ts +1 -1
- package/dist/theme-imports-lvBw3AD1.js +109 -0
- package/dist/transform-Ct2OMkFN.d.ts +153 -0
- package/dist/whatsapp.d.ts +57 -0
- package/dist/whatsapp.js +22 -0
- package/generated/contract.json +1782 -0
- package/generated/reference.md +353 -0
- package/package.json +41 -151
- package/skills/sml/SKILL.md +123 -0
- package/dist/ast-BrDtApwS.d.ts +0 -107
- package/dist/ast.d.ts +0 -2
- package/dist/ast.js +0 -124
- package/dist/candidate.d.ts +0 -29
- package/dist/candidate.js +0 -88
- package/dist/catalog-CKuOooB8.d.ts +0 -354
- package/dist/checks-D73Xizbw.js +0 -59366
- package/dist/checks.d.ts +0 -33
- package/dist/checks.js +0 -2
- package/dist/classes.d.ts +0 -26
- package/dist/classes.js +0 -184
- package/dist/compile-sms.d.ts +0 -28
- package/dist/compile-sms.js +0 -44
- package/dist/compile-whatsapp.d.ts +0 -56
- package/dist/compile-whatsapp.js +0 -186
- package/dist/compile.d.ts +0 -18
- package/dist/compile.js +0 -29
- package/dist/components.d.ts +0 -289
- package/dist/components.js +0 -106
- package/dist/conditionals.d.ts +0 -10
- package/dist/conditionals.js +0 -74
- package/dist/corpus-CPPfgAHN.d.ts +0 -106
- package/dist/diagnostics-OcQKQYSY.d.ts +0 -49
- package/dist/email/selection.d.ts +0 -2
- package/dist/email/selection.js +0 -91
- package/dist/email/source-edit.js +0 -185
- package/dist/email/stylesheets.d.ts +0 -15
- package/dist/email/stylesheets.js +0 -48
- package/dist/emit.d.ts +0 -51
- package/dist/emit.js +0 -151
- package/dist/interpolate.d.ts +0 -24
- package/dist/interpolate.js +0 -43
- package/dist/jsx-dev-runtime.d.ts +0 -11
- package/dist/jsx-dev-runtime.js +0 -15
- package/dist/jsx-runtime-I-1nCtl2.d.ts +0 -57
- package/dist/jsx-runtime.d.ts +0 -2
- package/dist/jsx-runtime.js +0 -121
- package/dist/lexer.d.ts +0 -60
- package/dist/lexer.js +0 -281
- package/dist/model.d.ts +0 -24
- package/dist/model.js +0 -77
- package/dist/ops-BvwqHl7V.js +0 -630
- package/dist/ops.d.ts +0 -114
- package/dist/ops.js +0 -2
- package/dist/parser-t29cHLVL.d.ts +0 -396
- package/dist/parser.d.ts +0 -2
- package/dist/parser.js +0 -859
- package/dist/predicate-BI6uRRM-.js +0 -549
- package/dist/predicate-M-05Nd7R.d.ts +0 -56
- package/dist/predicate.d.ts +0 -2
- package/dist/predicate.js +0 -2
- package/dist/printer.d.ts +0 -6
- package/dist/printer.js +0 -122
- package/dist/registry-Dcp6QEK_.d.ts +0 -73
- package/dist/registry.d.ts +0 -2
- package/dist/registry.js +0 -646
- package/dist/render-template.d.ts +0 -45
- package/dist/render-template.js +0 -76
- package/dist/selection-CCArx9lU.d.ts +0 -91
- package/dist/showcase-egkusQBf.d.ts +0 -415
- package/dist/sms-segments-y4FpPilJ.d.ts +0 -13
- package/dist/source-locations-DrCM82Ym.d.ts +0 -67
- package/dist/starters/category-mapping.d.ts +0 -13
- package/dist/starters/category-mapping.js +0 -49
- package/dist/starters/corpus.d.ts +0 -2
- package/dist/starters/corpus.js +0 -194
- package/dist/starters/library.d.ts +0 -18
- package/dist/starters/library.js +0 -692
- package/dist/starters/public-gallery.d.ts +0 -266
- package/dist/starters/public-gallery.js +0 -916
- package/dist/starters/public-handoff.d.ts +0 -164
- package/dist/starters/public-handoff.js +0 -361
- package/dist/starters/public-preview-artifacts.d.ts +0 -43
- package/dist/starters/public-preview-artifacts.js +0 -104
- package/dist/starters/public-preview.d.ts +0 -61
- package/dist/starters/public-preview.js +0 -83
- package/dist/starters/showcase.d.ts +0 -2
- package/dist/starters/showcase.js +0 -335
- package/dist/starters/sources.d.ts +0 -45
- package/dist/starters/sources.js +0 -7762
- package/dist/starters/theme.d.ts +0 -45
- package/dist/starters/theme.js +0 -77
- package/dist/starters.d.ts +0 -2
- package/dist/starters.js +0 -874
- package/dist/tailwind-BYq7Lo6u.js +0 -727
- package/dist/template-DWftqh4x.d.ts +0 -60
- package/dist/tsx-source-instrumentation.d.ts +0 -30
- package/dist/tsx-source-instrumentation.js +0 -115
- package/dist/tsx-tracer.d.ts +0 -112
- package/dist/tsx-tracer.js +0 -985
- package/dist/variables-J_L3qxRa.d.ts +0 -77
- package/dist/variables.d.ts +0 -2
- package/dist/variables.js +0 -141
- package/dist/version-BITr58kk.js +0 -96
- package/generated/sml-agent-contract.json +0 -709
- package/generated/sml-email-agent-contract.json +0 -615
- package/generated/sml-email-language-reference.md +0 -94
- package/generated/sml-language-reference.md +0 -195
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { l as Channel } from "../ast-BrDtApwS.js";
|
|
2
|
-
import { r as StarterCategory } from "../catalog-CKuOooB8.js";
|
|
3
|
-
import { TemplateCollectionId, TemplateUseCase } from "./public-gallery.js";
|
|
4
|
-
import { PublicTemplatePreview } from "./public-preview.js";
|
|
5
|
-
import { StarterProject } from "./sources.js";
|
|
6
|
-
import { Result } from "better-result";
|
|
7
|
-
//#region starters/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
|
-
}
|
|
13
|
-
/** Where an approved public artifact came from. */
|
|
14
|
-
interface TemplateReferenceProvenance {
|
|
15
|
-
readonly source: "samva-owned" | "sanitized-synthetic" | "reviewed-public" | "brand-recreation";
|
|
16
|
-
readonly brandRecreation?: TemplateBrandRecreation;
|
|
17
|
-
/** Stable identifier in the producing system; never a third-party URL. */
|
|
18
|
-
readonly referenceId: string;
|
|
19
|
-
/** Stable ISO-8601 timestamp supplied by the producer. */
|
|
20
|
-
readonly recordedAt: string;
|
|
21
|
-
}
|
|
22
|
-
interface ApprovedPublicTemplateArtifact {
|
|
23
|
-
readonly approval: {
|
|
24
|
-
readonly state: "draft" | "approved" | "rejected";
|
|
25
|
-
readonly approvedBy: string;
|
|
26
|
-
/** Stable ISO-8601 timestamp supplied by the owner. */
|
|
27
|
-
readonly approvedAt: string;
|
|
28
|
-
};
|
|
29
|
-
readonly starter: {
|
|
30
|
-
readonly id: string;
|
|
31
|
-
readonly channel: Channel;
|
|
32
|
-
readonly category: StarterCategory;
|
|
33
|
-
readonly name: string;
|
|
34
|
-
readonly description: string;
|
|
35
|
-
readonly variables: ReadonlyArray<string>;
|
|
36
|
-
readonly source: string;
|
|
37
|
-
readonly project?: StarterProject;
|
|
38
|
-
};
|
|
39
|
-
readonly gallery: {
|
|
40
|
-
readonly slug: string;
|
|
41
|
-
readonly useCase: TemplateUseCase;
|
|
42
|
-
readonly collections: ReadonlyArray<TemplateCollectionId>;
|
|
43
|
-
readonly relatedTemplates: ReadonlyArray<string>;
|
|
44
|
-
};
|
|
45
|
-
readonly preview: PublicTemplatePreview;
|
|
46
|
-
readonly publicMetadata: {
|
|
47
|
-
readonly cardTitle: string;
|
|
48
|
-
readonly cardDescription: string;
|
|
49
|
-
readonly guidance: ReadonlyArray<string>;
|
|
50
|
-
readonly variableDescriptions: Readonly<Record<string, string>>;
|
|
51
|
-
readonly seo: {
|
|
52
|
-
readonly title: string;
|
|
53
|
-
readonly description: string;
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
readonly provenance: TemplateReferenceProvenance;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* A destination-neutral template packet. Gallery and agent callers can use
|
|
60
|
-
* this shape without depending on a Lab corpus or a particular import source.
|
|
61
|
-
*/
|
|
62
|
-
interface TemplateReferencePacket {
|
|
63
|
-
readonly version: 1;
|
|
64
|
-
readonly starter: ApprovedPublicTemplateArtifact["starter"];
|
|
65
|
-
readonly gallery: ApprovedPublicTemplateArtifact["gallery"] & {
|
|
66
|
-
readonly path: `/templates/${string}`;
|
|
67
|
-
};
|
|
68
|
-
readonly preview: ApprovedPublicTemplateArtifact["preview"];
|
|
69
|
-
readonly publicMetadata: ApprovedPublicTemplateArtifact["publicMetadata"];
|
|
70
|
-
readonly provenance: TemplateReferenceProvenance;
|
|
71
|
-
}
|
|
72
|
-
interface ValidatedPublicTemplateArtifact {
|
|
73
|
-
readonly starter: Omit<ApprovedPublicTemplateArtifact["starter"], "source" | "project">;
|
|
74
|
-
readonly source: string;
|
|
75
|
-
readonly project?: StarterProject;
|
|
76
|
-
readonly gallery: ApprovedPublicTemplateArtifact["gallery"] & {
|
|
77
|
-
readonly path: `/templates/${string}`;
|
|
78
|
-
};
|
|
79
|
-
readonly preview: ApprovedPublicTemplateArtifact["preview"];
|
|
80
|
-
readonly publicMetadata: ApprovedPublicTemplateArtifact["publicMetadata"];
|
|
81
|
-
readonly approval: ApprovedPublicTemplateArtifact["approval"] & {
|
|
82
|
-
readonly state: "approved";
|
|
83
|
-
};
|
|
84
|
-
readonly reference: TemplateReferencePacket;
|
|
85
|
-
}
|
|
86
|
-
type PublicHandoffIssueCode = "approval" | "identity" | "duplicate-id" | "duplicate-slug" | "source" | "font-token" | "channel" | "variables" | "sample-data" | "assets" | "public-metadata" | "provenance" | "reference";
|
|
87
|
-
interface PublicHandoffIssue {
|
|
88
|
-
readonly artifactId: string;
|
|
89
|
-
readonly code: PublicHandoffIssueCode;
|
|
90
|
-
readonly message: string;
|
|
91
|
-
}
|
|
92
|
-
/** Bound authored source before parsing or mounting it. */
|
|
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: StarterProject) => boolean;
|
|
101
|
-
/** Digest the complete authored graph independently of caller file ordering. */
|
|
102
|
-
declare const publicTemplateProjectDigest: (project: Pick<StarterProject, "entryPath" | "files">) => Promise<string>;
|
|
103
|
-
declare const isValidTemplateBrandRecreation: (brandRecreation: TemplateBrandRecreation | undefined, project: StarterProject | undefined) => boolean;
|
|
104
|
-
/** Static syntax inventory; loops, branches and component expansion are not evaluated. */
|
|
105
|
-
interface PublicTemplateAuthoredStructure {
|
|
106
|
-
readonly basis: "authored-jsx";
|
|
107
|
-
readonly nodes: ReadonlyArray<{
|
|
108
|
-
readonly tag: string;
|
|
109
|
-
readonly depth: number;
|
|
110
|
-
readonly childIndex: number;
|
|
111
|
-
}>;
|
|
112
|
-
readonly components: ReadonlyArray<{
|
|
113
|
-
readonly tag: string;
|
|
114
|
-
readonly count: number;
|
|
115
|
-
}>;
|
|
116
|
-
readonly palette: ReadonlyArray<string>;
|
|
117
|
-
}
|
|
118
|
-
/** Only generic element names and literal class colors survive deidentification. */
|
|
119
|
-
declare const publicTemplateAuthoredStructure: (source: string) => PublicTemplateAuthoredStructure | undefined;
|
|
120
|
-
/**
|
|
121
|
-
* Public imports may only refer to the default email Tailwind font-family tokens. A source may
|
|
122
|
-
* still use the supported `font-*` weight utilities; this check deliberately
|
|
123
|
-
* rejects a new family token instead of creating a corpus font-hosting path.
|
|
124
|
-
*/
|
|
125
|
-
declare const unsupportedPublicTemplateFontTokens: (source: string) => ReadonlyArray<string>;
|
|
126
|
-
/**
|
|
127
|
-
* Accept bounded HTTPS URLs, same-origin gallery files under `/templates/assets/`,
|
|
128
|
-
* or the preview's self-contained image data. Live Unsplash, placehold, and
|
|
129
|
-
* assets.example hosts are never safe for public sample data.
|
|
130
|
-
*/
|
|
131
|
-
declare const isSafePublicTemplateAsset: (value: string) => boolean;
|
|
132
|
-
/** Normalizes one approved handoff for gallery and future agent consumers. */
|
|
133
|
-
declare const toTemplateReferencePacket: (artifact: ApprovedPublicTemplateArtifact) => TemplateReferencePacket;
|
|
134
|
-
type TemplateReferenceProjection = {
|
|
135
|
-
readonly kind: "source";
|
|
136
|
-
readonly relativePath: "starter.tsx";
|
|
137
|
-
readonly contents: string;
|
|
138
|
-
} | {
|
|
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`;
|
|
146
|
-
readonly contents: string;
|
|
147
|
-
};
|
|
148
|
-
/**
|
|
149
|
-
* Emits legacy projections plus the complete project as deterministic bytes. The importer
|
|
150
|
-
* owns no checked-in destination files; callers choose an explicit output dir.
|
|
151
|
-
*/
|
|
152
|
-
declare const emitTemplateReferencePacket: (packet: TemplateReferencePacket) => ReadonlyArray<TemplateReferenceProjection>;
|
|
153
|
-
/**
|
|
154
|
-
* Validates metadata and TSX syntax, never executing the template. The Vite importer
|
|
155
|
-
* verifies schema, fixtures and rendered content before writing projections.
|
|
156
|
-
* Validates explicitly supplied, owner-approved artifacts without filesystem,
|
|
157
|
-
* network, persistence, or discovery. Successful output is a projection for
|
|
158
|
-
* the existing starter, public-gallery, preview, and editorial contracts; this
|
|
159
|
-
* function never registers or publishes it.
|
|
160
|
-
*/
|
|
161
|
-
declare const validatePublicTemplateHandoff: (artifacts: ReadonlyArray<ApprovedPublicTemplateArtifact>) => Result<ReadonlyArray<ValidatedPublicTemplateArtifact>, ReadonlyArray<PublicHandoffIssue>>;
|
|
162
|
-
//#endregion
|
|
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 };
|
|
164
|
-
//# sourceMappingURL=public-handoff.d.ts.map
|
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
import { n as isObject } from "../guards-eV1qLbf2.js";
|
|
2
|
-
import { EMAIL_ELEMENTS } from "../email/elements.js";
|
|
3
|
-
import { starterCatalog } from "../starters.js";
|
|
4
|
-
import { publicTemplates, templateCollections } from "./public-gallery.js";
|
|
5
|
-
import { Result } from "better-result";
|
|
6
|
-
import { parse } from "@babel/parser";
|
|
7
|
-
//#region starters/public-handoff.ts
|
|
8
|
-
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
|
-
const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
10
|
-
const MAX_HTTPS_ASSET_LENGTH = 2048;
|
|
11
|
-
const MAX_DATA_IMAGE_ASSET_LENGTH = 32768;
|
|
12
|
-
const DATA_IMAGE = /^data:image\/(?:avif|gif|jpeg|png|svg\+xml|webp)(?:;charset=[a-z0-9-]+)?(?:;base64)?,[^\r\n]+$/i;
|
|
13
|
-
const PUBLIC_TEMPLATE_ASSET_PATH = /^\/templates\/assets\/[A-Za-z0-9][A-Za-z0-9._-]*\.(?:jpe?g|png|webp)$/;
|
|
14
|
-
const BLOCKED_PUBLIC_ASSET = /(?:unsplash|placehold|assets\.example)/i;
|
|
15
|
-
/** A sample value is non-empty when every string in it is, and no list or object is empty. */
|
|
16
|
-
const nonEmptySample = (value) => {
|
|
17
|
-
if (typeof value === "string") return nonEmpty(value);
|
|
18
|
-
if (Array.isArray(value)) return value.length > 0 && value.every(nonEmptySample);
|
|
19
|
-
if (isObject(value)) {
|
|
20
|
-
const values = Object.values(value);
|
|
21
|
-
return values.length > 0 && values.every(nonEmptySample);
|
|
22
|
-
}
|
|
23
|
-
return true;
|
|
24
|
-
};
|
|
25
|
-
const isTemplateReferenceProvenanceSource = (value) => value === "samva-owned" || value === "sanitized-synthetic" || value === "reviewed-public" || value === "brand-recreation";
|
|
26
|
-
/** Bound authored source before parsing or mounting it. */
|
|
27
|
-
const PUBLIC_TEMPLATE_SOURCE_MAX_CHARACTERS = 512e3;
|
|
28
|
-
/** Bounds apply to decoded bytes, before evaluation or disk emission. */
|
|
29
|
-
const PUBLIC_TEMPLATE_PROJECT_MAX_BYTES = 8388608;
|
|
30
|
-
const PUBLIC_TEMPLATE_PROJECT_FILE_MAX_BYTES = 2097152;
|
|
31
|
-
const PUBLIC_TEMPLATE_PROJECT_MAX_FILES = 64;
|
|
32
|
-
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");
|
|
33
|
-
/** Reject traversal, collisions, malformed base64 and oversized authored projects. */
|
|
34
|
-
const isSafePublicTemplateProject = (project) => {
|
|
35
|
-
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;
|
|
36
|
-
const paths = /* @__PURE__ */ new Set();
|
|
37
|
-
let total = 0;
|
|
38
|
-
for (const file of project.files) {
|
|
39
|
-
if (!isSafePublicTemplateProjectPath(file.path) || paths.has(file.path.toLowerCase())) return false;
|
|
40
|
-
paths.add(file.path.toLowerCase());
|
|
41
|
-
let bytes;
|
|
42
|
-
if (file.encoding === "utf8") {
|
|
43
|
-
if (file.content.length > 512e3) return false;
|
|
44
|
-
bytes = new TextEncoder().encode(file.content).length;
|
|
45
|
-
} else if (file.encoding === "base64") {
|
|
46
|
-
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;
|
|
47
|
-
if (btoa(atob(file.content)) !== file.content) return false;
|
|
48
|
-
bytes = file.content.length / 4 * 3 - (file.content.endsWith("==") ? 2 : file.content.endsWith("=") ? 1 : 0);
|
|
49
|
-
} else return false;
|
|
50
|
-
if (bytes > 2097152) return false;
|
|
51
|
-
total += bytes;
|
|
52
|
-
if (total > 8388608) return false;
|
|
53
|
-
}
|
|
54
|
-
for (const path of paths) if (path.split("/").slice(0, -1).some((_, index, parts) => paths.has(parts.slice(0, index + 1).join("/")))) return false;
|
|
55
|
-
return project.files.some((file) => file.path === project.entryPath && file.encoding === "utf8");
|
|
56
|
-
};
|
|
57
|
-
/** Digest the complete authored graph independently of caller file ordering. */
|
|
58
|
-
const publicTemplateProjectDigest = async (project) => {
|
|
59
|
-
const serialized = JSON.stringify({
|
|
60
|
-
entryPath: project.entryPath,
|
|
61
|
-
files: [...project.files].sort((a, b) => a.path.localeCompare(b.path)).map(({ path, content, encoding }) => ({
|
|
62
|
-
path,
|
|
63
|
-
content,
|
|
64
|
-
encoding
|
|
65
|
-
}))
|
|
66
|
-
});
|
|
67
|
-
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialized)));
|
|
68
|
-
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
69
|
-
};
|
|
70
|
-
const isValidTemplateBrandRecreation = (brandRecreation, project) => brandRecreation !== void 0 && project !== void 0 && brandRecreation.brand.trim().length > 0 && brandRecreation.subject.trim().length > 0;
|
|
71
|
-
const PUBLIC_TEMPLATE_FONT_TOKENS = /* @__PURE__ */ new Set([
|
|
72
|
-
"sans",
|
|
73
|
-
"serif",
|
|
74
|
-
"mono"
|
|
75
|
-
]);
|
|
76
|
-
const FONT_WEIGHT_TOKENS = /* @__PURE__ */ new Set([
|
|
77
|
-
"thin",
|
|
78
|
-
"extralight",
|
|
79
|
-
"light",
|
|
80
|
-
"normal",
|
|
81
|
-
"medium",
|
|
82
|
-
"semibold",
|
|
83
|
-
"bold",
|
|
84
|
-
"extrabold",
|
|
85
|
-
"black"
|
|
86
|
-
]);
|
|
87
|
-
const parsePublicTemplateSource = (source) => {
|
|
88
|
-
if (source.length === 0 || source.length > 512e3) return void 0;
|
|
89
|
-
try {
|
|
90
|
-
const parsed = parse(source, {
|
|
91
|
-
sourceType: "module",
|
|
92
|
-
plugins: ["typescript", "jsx"]
|
|
93
|
-
});
|
|
94
|
-
return parsed.program.body.some((node) => node.type === "ExportDefaultDeclaration") ? parsed : void 0;
|
|
95
|
-
} catch {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const PUBLIC_COMPONENT_TAGS = /* @__PURE__ */ new Set([
|
|
100
|
-
...Object.keys(EMAIL_ELEMENTS),
|
|
101
|
-
"Email",
|
|
102
|
-
"Section",
|
|
103
|
-
"Columns",
|
|
104
|
-
"Column",
|
|
105
|
-
"Button",
|
|
106
|
-
"Spacer",
|
|
107
|
-
"Divider",
|
|
108
|
-
"Image",
|
|
109
|
-
"Link"
|
|
110
|
-
]);
|
|
111
|
-
/** Only generic element names and literal class colors survive deidentification. */
|
|
112
|
-
const publicTemplateAuthoredStructure = (source) => {
|
|
113
|
-
const parsed = parsePublicTemplateSource(source);
|
|
114
|
-
if (parsed === void 0) return void 0;
|
|
115
|
-
const nodes = [];
|
|
116
|
-
const counts = /* @__PURE__ */ new Map();
|
|
117
|
-
const palette = /* @__PURE__ */ new Set();
|
|
118
|
-
const siblings = /* @__PURE__ */ new Map();
|
|
119
|
-
const walk = (value, depth) => {
|
|
120
|
-
if (Array.isArray(value)) {
|
|
121
|
-
for (const item of value) walk(item, depth);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (!isObject(value)) return;
|
|
125
|
-
let childDepth = depth;
|
|
126
|
-
if (value.type === "JSXElement") {
|
|
127
|
-
const opening = value.openingElement;
|
|
128
|
-
if (!isObject(opening)) return;
|
|
129
|
-
const name = opening.name;
|
|
130
|
-
const tag = isObject(name) && typeof name.name === "string" && PUBLIC_COMPONENT_TAGS.has(name.name) ? name.name : "Component";
|
|
131
|
-
const childIndex = siblings.get(depth) ?? 0;
|
|
132
|
-
siblings.set(depth, childIndex + 1);
|
|
133
|
-
siblings.set(depth + 1, 0);
|
|
134
|
-
if (nodes.length < 200) nodes.push({
|
|
135
|
-
tag,
|
|
136
|
-
depth,
|
|
137
|
-
childIndex
|
|
138
|
-
});
|
|
139
|
-
counts.set(tag, (counts.get(tag) ?? 0) + 1);
|
|
140
|
-
if (Array.isArray(opening.attributes)) for (const attribute of opening.attributes) {
|
|
141
|
-
if (!isObject(attribute) || !isObject(attribute.name) || attribute.name.name !== "className") continue;
|
|
142
|
-
const literal = attribute.value;
|
|
143
|
-
if (!isObject(literal) || literal.type !== "StringLiteral" || typeof literal.value !== "string") continue;
|
|
144
|
-
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()}`);
|
|
145
|
-
}
|
|
146
|
-
childDepth += 1;
|
|
147
|
-
}
|
|
148
|
-
for (const [key, child] of Object.entries(value)) {
|
|
149
|
-
if ([
|
|
150
|
-
"loc",
|
|
151
|
-
"comments",
|
|
152
|
-
"leadingComments",
|
|
153
|
-
"trailingComments",
|
|
154
|
-
"innerComments",
|
|
155
|
-
"extra",
|
|
156
|
-
"tokens"
|
|
157
|
-
].includes(key)) continue;
|
|
158
|
-
walk(child, childDepth);
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
walk(parsed.program, 0);
|
|
162
|
-
return {
|
|
163
|
-
basis: "authored-jsx",
|
|
164
|
-
nodes,
|
|
165
|
-
components: [...counts].sort(([a], [b]) => a.localeCompare(b)).slice(0, 64).map(([tag, count]) => ({
|
|
166
|
-
tag,
|
|
167
|
-
count
|
|
168
|
-
})),
|
|
169
|
-
palette: [...palette].sort()
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
const sortedUnique = (values) => [...new Set(values)].sort();
|
|
173
|
-
const sameStrings = (left, right) => JSON.stringify(sortedUnique(left)) === JSON.stringify(sortedUnique(right));
|
|
174
|
-
const nonEmpty = (value) => value.trim().length > 0;
|
|
175
|
-
const issue = (artifactId, code, message) => ({
|
|
176
|
-
artifactId,
|
|
177
|
-
code,
|
|
178
|
-
message
|
|
179
|
-
});
|
|
180
|
-
/**
|
|
181
|
-
* Public imports may only refer to the default email Tailwind font-family tokens. A source may
|
|
182
|
-
* still use the supported `font-*` weight utilities; this check deliberately
|
|
183
|
-
* rejects a new family token instead of creating a corpus font-hosting path.
|
|
184
|
-
*/
|
|
185
|
-
const unsupportedPublicTemplateFontTokens = (source) => {
|
|
186
|
-
const unsupported = /* @__PURE__ */ new Set();
|
|
187
|
-
for (const match of source.matchAll(/\bclassName\s*=\s*(["'])(.*?)\1/gs)) for (const className of (match[2] ?? "").split(/\s+/)) {
|
|
188
|
-
if (!className.startsWith("font-")) continue;
|
|
189
|
-
const token = className.slice(5);
|
|
190
|
-
if (!PUBLIC_TEMPLATE_FONT_TOKENS.has(token) && !FONT_WEIGHT_TOKENS.has(token)) unsupported.add(token);
|
|
191
|
-
}
|
|
192
|
-
return [...unsupported].sort();
|
|
193
|
-
};
|
|
194
|
-
/**
|
|
195
|
-
* Accept bounded HTTPS URLs, same-origin gallery files under `/templates/assets/`,
|
|
196
|
-
* or the preview's self-contained image data. Live Unsplash, placehold, and
|
|
197
|
-
* assets.example hosts are never safe for public sample data.
|
|
198
|
-
*/
|
|
199
|
-
const isSafePublicTemplateAsset = (value) => {
|
|
200
|
-
if (value.includes("..") || BLOCKED_PUBLIC_ASSET.test(value)) return false;
|
|
201
|
-
if (value.startsWith("data:")) return value.length <= MAX_DATA_IMAGE_ASSET_LENGTH && DATA_IMAGE.test(value);
|
|
202
|
-
if (PUBLIC_TEMPLATE_ASSET_PATH.test(value)) return value.length <= MAX_HTTPS_ASSET_LENGTH;
|
|
203
|
-
const url = URL.parse(value);
|
|
204
|
-
return value.length <= MAX_HTTPS_ASSET_LENGTH && url?.protocol === "https:" && url.hostname.length > 0 && url.username.length === 0 && url.password.length === 0;
|
|
205
|
-
};
|
|
206
|
-
/** Normalizes one approved handoff for gallery and future agent consumers. */
|
|
207
|
-
const toTemplateReferencePacket = (artifact) => ({
|
|
208
|
-
version: 1,
|
|
209
|
-
starter: artifact.starter,
|
|
210
|
-
gallery: {
|
|
211
|
-
...artifact.gallery,
|
|
212
|
-
path: `/templates/${artifact.gallery.slug}`
|
|
213
|
-
},
|
|
214
|
-
preview: artifact.preview,
|
|
215
|
-
publicMetadata: artifact.publicMetadata,
|
|
216
|
-
provenance: artifact.provenance
|
|
217
|
-
});
|
|
218
|
-
const jsonProjection = (kind, value) => ({
|
|
219
|
-
kind,
|
|
220
|
-
relativePath: `${kind}.json`,
|
|
221
|
-
contents: `${JSON.stringify(value, null, 2)}\n`
|
|
222
|
-
});
|
|
223
|
-
/**
|
|
224
|
-
* Emits legacy projections plus the complete project as deterministic bytes. The importer
|
|
225
|
-
* owns no checked-in destination files; callers choose an explicit output dir.
|
|
226
|
-
*/
|
|
227
|
-
const emitTemplateReferencePacket = (packet) => {
|
|
228
|
-
const project = packet.starter.project;
|
|
229
|
-
if (project !== void 0 && (!isSafePublicTemplateProject(project) || project.files.find((file) => file.path === project.entryPath)?.content !== packet.starter.source)) throw new Error("Invalid public template project");
|
|
230
|
-
if (packet.provenance.source === "brand-recreation" && !isValidTemplateBrandRecreation(packet.provenance.brandRecreation, project)) throw new Error("Invalid public template brand recreation attribution");
|
|
231
|
-
return [
|
|
232
|
-
...project === void 0 ? [] : [jsonProjection("project", {
|
|
233
|
-
...project,
|
|
234
|
-
files: [...project.files].sort((a, b) => a.path.localeCompare(b.path))
|
|
235
|
-
}), ...[...project.files].sort((a, b) => a.path.localeCompare(b.path)).map((file) => ({
|
|
236
|
-
kind: "project-file",
|
|
237
|
-
relativePath: `project/${file.path}`,
|
|
238
|
-
contents: file.content,
|
|
239
|
-
encoding: file.encoding
|
|
240
|
-
}))],
|
|
241
|
-
{
|
|
242
|
-
kind: "source",
|
|
243
|
-
relativePath: "starter.tsx",
|
|
244
|
-
contents: packet.starter.source
|
|
245
|
-
},
|
|
246
|
-
jsonProjection("catalog", {
|
|
247
|
-
id: packet.starter.id,
|
|
248
|
-
channel: packet.starter.channel,
|
|
249
|
-
category: packet.starter.category,
|
|
250
|
-
name: packet.starter.name,
|
|
251
|
-
description: packet.starter.description,
|
|
252
|
-
variables: packet.starter.variables
|
|
253
|
-
}),
|
|
254
|
-
jsonProjection("gallery", {
|
|
255
|
-
starterId: packet.starter.id,
|
|
256
|
-
...packet.gallery,
|
|
257
|
-
provenance: packet.provenance
|
|
258
|
-
}),
|
|
259
|
-
jsonProjection("preview", packet.preview),
|
|
260
|
-
jsonProjection("editorial", {
|
|
261
|
-
cardTitle: packet.publicMetadata.cardTitle,
|
|
262
|
-
cardDescription: packet.publicMetadata.cardDescription,
|
|
263
|
-
guidance: packet.publicMetadata.guidance,
|
|
264
|
-
variables: packet.publicMetadata.variableDescriptions,
|
|
265
|
-
seo: packet.publicMetadata.seo
|
|
266
|
-
})
|
|
267
|
-
];
|
|
268
|
-
};
|
|
269
|
-
/**
|
|
270
|
-
* Validates metadata and TSX syntax, never executing the template. The Vite importer
|
|
271
|
-
* verifies schema, fixtures and rendered content before writing projections.
|
|
272
|
-
* Validates explicitly supplied, owner-approved artifacts without filesystem,
|
|
273
|
-
* network, persistence, or discovery. Successful output is a projection for
|
|
274
|
-
* the existing starter, public-gallery, preview, and editorial contracts; this
|
|
275
|
-
* function never registers or publishes it.
|
|
276
|
-
*/
|
|
277
|
-
const validatePublicTemplateHandoff = (artifacts) => {
|
|
278
|
-
const issues = [];
|
|
279
|
-
const existingIds = new Set(starterCatalog.map((starter) => starter.id));
|
|
280
|
-
const existingSlugs = new Set(publicTemplates.map((template) => template.slug));
|
|
281
|
-
const suppliedIds = /* @__PURE__ */ new Set();
|
|
282
|
-
const suppliedSlugs = /* @__PURE__ */ new Set();
|
|
283
|
-
const allKnownIds = /* @__PURE__ */ new Set([...publicTemplates.map((template) => template.starterId), ...artifacts.map((artifact) => artifact.starter.id)]);
|
|
284
|
-
const knownCollections = new Set(templateCollections.map((collection) => collection.id));
|
|
285
|
-
for (const artifact of artifacts) {
|
|
286
|
-
const id = artifact.starter.id;
|
|
287
|
-
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"));
|
|
288
|
-
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"));
|
|
289
|
-
if (!SLUG.test(id) || !SLUG.test(artifact.gallery.slug)) issues.push(issue(id, "identity", "starter id and canonical slug must be lowercase kebab-case"));
|
|
290
|
-
if (existingIds.has(id) || suppliedIds.has(id)) issues.push(issue(id, "duplicate-id", `starter id "${id}" is already present`));
|
|
291
|
-
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`));
|
|
292
|
-
suppliedIds.add(id);
|
|
293
|
-
suppliedSlugs.add(artifact.gallery.slug);
|
|
294
|
-
const project = artifact.starter.project;
|
|
295
|
-
if (project !== void 0 && (!isSafePublicTemplateProject(project) || project.files.find((file) => file.path === project.entryPath)?.content !== artifact.starter.source)) issues.push(issue(id, "source", "project must contain safe bounded unique files and the matching UTF-8 entry source"));
|
|
296
|
-
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"));
|
|
297
|
-
if (parsePublicTemplateSource(artifact.starter.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"));
|
|
298
|
-
if (artifact.starter.channel !== "email") issues.push(issue(id, "channel", "public template source must be an executable email"));
|
|
299
|
-
if (new Set(artifact.starter.variables).size !== artifact.starter.variables.length || artifact.starter.variables.some((value) => !nonEmpty(value))) issues.push(issue(id, "variables", "declared input fields must be non-empty and unique"));
|
|
300
|
-
const unsupportedFontTokens = unsupportedPublicTemplateFontTokens(artifact.starter.source);
|
|
301
|
-
if (unsupportedFontTokens.length > 0) issues.push(issue(id, "font-token", `unsupported public font token: ${unsupportedFontTokens.join(", ")}`));
|
|
302
|
-
const declaredVariables = new Set(artifact.starter.variables);
|
|
303
|
-
const undeclaredSample = Object.keys(artifact.preview.sampleData).filter((variable) => !declaredVariables.has(variable));
|
|
304
|
-
if (undeclaredSample.length > 0) issues.push(issue(id, "sample-data", `sample data supplies undeclared variables: ${sortedUnique(undeclaredSample).join(", ")}`));
|
|
305
|
-
if (Object.values(artifact.preview.sampleData).some((value) => !nonEmptySample(value))) issues.push(issue(id, "sample-data", "sample data values must be non-empty"));
|
|
306
|
-
const assetVariables = artifact.preview.assets.map((asset) => asset.variable);
|
|
307
|
-
if (new Set(assetVariables).size !== assetVariables.length) issues.push(issue(id, "assets", "asset variable references must be unique"));
|
|
308
|
-
for (const asset of artifact.preview.assets) {
|
|
309
|
-
const value = artifact.preview.sampleData[asset.variable];
|
|
310
|
-
if (!artifact.starter.variables.includes(asset.variable)) issues.push(issue(id, "assets", `asset "${asset.variable}" is not a declared starter variable`));
|
|
311
|
-
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`));
|
|
312
|
-
}
|
|
313
|
-
const metadata = artifact.publicMetadata;
|
|
314
|
-
const metadataStrings = [
|
|
315
|
-
artifact.starter.name,
|
|
316
|
-
artifact.starter.description,
|
|
317
|
-
artifact.preview.subject,
|
|
318
|
-
artifact.preview.preheader,
|
|
319
|
-
metadata.cardTitle,
|
|
320
|
-
metadata.cardDescription,
|
|
321
|
-
metadata.seo.title,
|
|
322
|
-
metadata.seo.description,
|
|
323
|
-
...metadata.guidance,
|
|
324
|
-
...Object.values(metadata.variableDescriptions)
|
|
325
|
-
];
|
|
326
|
-
if (metadata.guidance.length === 0 || metadataStrings.some((value) => !nonEmpty(value))) issues.push(issue(id, "public-metadata", "public metadata fields must be non-empty"));
|
|
327
|
-
if (!sameStrings(Object.keys(metadata.variableDescriptions), artifact.starter.variables)) issues.push(issue(id, "public-metadata", "variable descriptions must cover every declared variable exactly"));
|
|
328
|
-
if (new Set(artifact.gallery.collections).size !== artifact.gallery.collections.length) issues.push(issue(id, "reference", "collection references must be unique"));
|
|
329
|
-
for (const collectionId of artifact.gallery.collections) if (!knownCollections.has(collectionId)) issues.push(issue(id, "reference", `collection "${collectionId}" is invalid`));
|
|
330
|
-
if (new Set(artifact.gallery.relatedTemplates).size !== artifact.gallery.relatedTemplates.length) issues.push(issue(id, "reference", "related template references must be unique"));
|
|
331
|
-
for (const relatedId of artifact.gallery.relatedTemplates) if (relatedId === id || !allKnownIds.has(relatedId)) issues.push(issue(id, "reference", `related template "${relatedId}" is invalid`));
|
|
332
|
-
}
|
|
333
|
-
if (issues.length > 0) return Result.err(issues);
|
|
334
|
-
return Result.ok(artifacts.map((artifact) => {
|
|
335
|
-
const reference = toTemplateReferencePacket(artifact);
|
|
336
|
-
return {
|
|
337
|
-
approval: {
|
|
338
|
-
...artifact.approval,
|
|
339
|
-
state: "approved"
|
|
340
|
-
},
|
|
341
|
-
starter: {
|
|
342
|
-
id: artifact.starter.id,
|
|
343
|
-
channel: artifact.starter.channel,
|
|
344
|
-
category: artifact.starter.category,
|
|
345
|
-
name: artifact.starter.name,
|
|
346
|
-
description: artifact.starter.description,
|
|
347
|
-
variables: artifact.starter.variables
|
|
348
|
-
},
|
|
349
|
-
source: artifact.starter.source,
|
|
350
|
-
...artifact.starter.project === void 0 ? {} : { project: artifact.starter.project },
|
|
351
|
-
gallery: reference.gallery,
|
|
352
|
-
preview: artifact.preview,
|
|
353
|
-
publicMetadata: artifact.publicMetadata,
|
|
354
|
-
reference
|
|
355
|
-
};
|
|
356
|
-
}));
|
|
357
|
-
};
|
|
358
|
-
//#endregion
|
|
359
|
-
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 };
|
|
360
|
-
|
|
361
|
-
//# sourceMappingURL=public-handoff.js.map
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { t as EmailStarterId } from "../catalog-CKuOooB8.js";
|
|
2
|
-
import { PublicTemplate } from "./public-gallery.js";
|
|
3
|
-
import { PreviewQuality } from "./public-preview.js";
|
|
4
|
-
import { Result } from "better-result";
|
|
5
|
-
//#region starters/public-preview-artifacts.d.ts
|
|
6
|
-
type PreviewSurface = "card" | "detail" | "og";
|
|
7
|
-
type PreviewViewport = "desktop" | "mobile";
|
|
8
|
-
type PreviewColorScheme = "light" | "dark";
|
|
9
|
-
interface PublicTemplateRenderInput {
|
|
10
|
-
readonly name: `${string}.${PreviewSurface}.${PreviewViewport}.${PreviewColorScheme}.html`;
|
|
11
|
-
readonly surface: PreviewSurface;
|
|
12
|
-
readonly viewport: PreviewViewport;
|
|
13
|
-
readonly colorScheme: PreviewColorScheme;
|
|
14
|
-
readonly width: number;
|
|
15
|
-
readonly height: number;
|
|
16
|
-
readonly html: string;
|
|
17
|
-
}
|
|
18
|
-
interface PublicTemplatePreviewArtifact {
|
|
19
|
-
readonly starterId: EmailStarterId;
|
|
20
|
-
readonly slug: string;
|
|
21
|
-
readonly subject: string;
|
|
22
|
-
readonly preheader: string;
|
|
23
|
-
readonly quality: PreviewQuality;
|
|
24
|
-
readonly source: string;
|
|
25
|
-
readonly html: string;
|
|
26
|
-
/** The delivered plain-text alternative of the same executed fixture. */
|
|
27
|
-
readonly text: string;
|
|
28
|
-
readonly assets: ReadonlyArray<string>;
|
|
29
|
-
readonly renders: ReadonlyArray<PublicTemplateRenderInput>;
|
|
30
|
-
}
|
|
31
|
-
interface PreviewArtifactBuildIssue {
|
|
32
|
-
readonly starterId: EmailStarterId;
|
|
33
|
-
readonly message: string;
|
|
34
|
-
}
|
|
35
|
-
/** Build one static preview artifact without loading the editor runtime or performing I/O. */
|
|
36
|
-
declare const buildPublicTemplatePreviewArtifact: (template: PublicTemplate) => Result<PublicTemplatePreviewArtifact, PreviewArtifactBuildIssue>;
|
|
37
|
-
/** Build only the requested canonical subset, preserving manifest order and aggregated failures. */
|
|
38
|
-
declare const buildPublicTemplatePreviewArtifactsFor: (templates: ReadonlyArray<PublicTemplate>) => Result<ReadonlyArray<PublicTemplatePreviewArtifact>, ReadonlyArray<PreviewArtifactBuildIssue>>;
|
|
39
|
-
/** Build the complete canonical public gallery. */
|
|
40
|
-
declare const buildPublicTemplatePreviewArtifacts: () => Result<readonly PublicTemplatePreviewArtifact[], readonly PreviewArtifactBuildIssue[]>;
|
|
41
|
-
//#endregion
|
|
42
|
-
export { PreviewArtifactBuildIssue, PreviewColorScheme, PreviewSurface, PreviewViewport, PublicTemplatePreviewArtifact, PublicTemplateRenderInput, buildPublicTemplatePreviewArtifact, buildPublicTemplatePreviewArtifacts, buildPublicTemplatePreviewArtifactsFor };
|
|
43
|
-
//# sourceMappingURL=public-preview-artifacts.d.ts.map
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { starterCatalog } from "../starters.js";
|
|
2
|
-
import { registerPublicCorpusEntry } from "./corpus.js";
|
|
3
|
-
import { publicTemplates } from "./public-gallery.js";
|
|
4
|
-
import { starterEmailAssets, starterEmailFixtures, starterModuleSource } from "./sources.js";
|
|
5
|
-
import { publicTemplatePreviews } from "./public-preview.js";
|
|
6
|
-
import { Result } from "better-result";
|
|
7
|
-
//#region starters/public-preview-artifacts.ts
|
|
8
|
-
const renderFrames = [
|
|
9
|
-
{
|
|
10
|
-
surface: "card",
|
|
11
|
-
viewport: "desktop",
|
|
12
|
-
width: 600,
|
|
13
|
-
height: 760
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
surface: "card",
|
|
17
|
-
viewport: "mobile",
|
|
18
|
-
width: 390,
|
|
19
|
-
height: 680
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
surface: "detail",
|
|
23
|
-
viewport: "desktop",
|
|
24
|
-
width: 720,
|
|
25
|
-
height: 900
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
surface: "detail",
|
|
29
|
-
viewport: "mobile",
|
|
30
|
-
width: 390,
|
|
31
|
-
height: 844
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
surface: "og",
|
|
35
|
-
viewport: "desktop",
|
|
36
|
-
width: 1200,
|
|
37
|
-
height: 630
|
|
38
|
-
}
|
|
39
|
-
];
|
|
40
|
-
const renderInputs = (slug, html) => renderFrames.flatMap((frame) => ["light", "dark"].map((colorScheme) => ({
|
|
41
|
-
...frame,
|
|
42
|
-
colorScheme,
|
|
43
|
-
name: `${slug}.${frame.surface}.${frame.viewport}.${colorScheme}.html`,
|
|
44
|
-
html
|
|
45
|
-
})));
|
|
46
|
-
/** Build one static preview artifact without loading the editor runtime or performing I/O. */
|
|
47
|
-
const buildPublicTemplatePreviewArtifact = (template) => {
|
|
48
|
-
const registration = registerPublicCorpusEntry({
|
|
49
|
-
id: template.starterId,
|
|
50
|
-
corpus: template.corpus,
|
|
51
|
-
facets: template.facets
|
|
52
|
-
});
|
|
53
|
-
if (Result.isError(registration)) return Result.err({
|
|
54
|
-
starterId: template.starterId,
|
|
55
|
-
message: `public corpus registration failed: ${registration.error.message}`
|
|
56
|
-
});
|
|
57
|
-
const source = starterModuleSource(template.starterId);
|
|
58
|
-
const fixture = starterEmailFixtures[template.starterId]?.default;
|
|
59
|
-
const starter = starterCatalog.find((entry) => entry.id === template.starterId);
|
|
60
|
-
const preview = publicTemplatePreviews[template.starterId];
|
|
61
|
-
if (source === void 0 || starter === void 0 || fixture === void 0) return Result.err({
|
|
62
|
-
starterId: template.starterId,
|
|
63
|
-
message: "canonical starter source is missing"
|
|
64
|
-
});
|
|
65
|
-
const sampleData = fixture.input;
|
|
66
|
-
const assets = new Set(starterEmailAssets[template.starterId]);
|
|
67
|
-
for (const asset of preview.assets) {
|
|
68
|
-
const value = sampleData[asset.variable];
|
|
69
|
-
if (typeof value !== "string") return Result.err({
|
|
70
|
-
starterId: template.starterId,
|
|
71
|
-
message: `sample data is missing a string asset: ${asset.variable}`
|
|
72
|
-
});
|
|
73
|
-
assets.add(value);
|
|
74
|
-
}
|
|
75
|
-
return Result.ok({
|
|
76
|
-
starterId: template.starterId,
|
|
77
|
-
slug: template.slug,
|
|
78
|
-
subject: fixture.subject,
|
|
79
|
-
preheader: fixture.preheader,
|
|
80
|
-
quality: preview.quality,
|
|
81
|
-
source,
|
|
82
|
-
html: fixture.html,
|
|
83
|
-
text: fixture.text,
|
|
84
|
-
assets: [...assets],
|
|
85
|
-
renders: renderInputs(template.slug, fixture.html)
|
|
86
|
-
});
|
|
87
|
-
};
|
|
88
|
-
/** Build only the requested canonical subset, preserving manifest order and aggregated failures. */
|
|
89
|
-
const buildPublicTemplatePreviewArtifactsFor = (templates) => {
|
|
90
|
-
const artifacts = [];
|
|
91
|
-
const issues = [];
|
|
92
|
-
for (const template of templates) {
|
|
93
|
-
const artifact = buildPublicTemplatePreviewArtifact(template);
|
|
94
|
-
if (Result.isOk(artifact)) artifacts.push(artifact.value);
|
|
95
|
-
else issues.push(artifact.error);
|
|
96
|
-
}
|
|
97
|
-
return issues.length > 0 ? Result.err(issues) : Result.ok(artifacts);
|
|
98
|
-
};
|
|
99
|
-
/** Build the complete canonical public gallery. */
|
|
100
|
-
const buildPublicTemplatePreviewArtifacts = () => buildPublicTemplatePreviewArtifactsFor(publicTemplates);
|
|
101
|
-
//#endregion
|
|
102
|
-
export { buildPublicTemplatePreviewArtifact, buildPublicTemplatePreviewArtifacts, buildPublicTemplatePreviewArtifactsFor };
|
|
103
|
-
|
|
104
|
-
//# sourceMappingURL=public-preview-artifacts.js.map
|