@samva/markup 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast.js +1 -1
- package/dist/{checks-CW0nFj5n.js → checks-D-zFQt5R.js} +2 -2
- package/dist/checks.js +1 -1
- package/dist/compile-sms.d.ts +1 -1
- package/dist/compile-whatsapp.d.ts +1 -1
- package/dist/components.d.ts +3 -3
- package/dist/conditionals.js +1 -1
- package/dist/diagnostics.js +2 -2
- package/dist/jsx-dev-runtime.d.ts +1 -1
- package/dist/{jsx-runtime-fSxcvlx4.d.ts → jsx-runtime-eNIMY87F.d.ts} +2 -2
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/ops.js +2 -2
- package/dist/{parser-yzjUOGhc.d.ts → parser-DkQKIjDa.d.ts} +11 -2
- 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/category-mapping.d.ts +1 -1
- package/dist/presets/public-gallery.d.ts +2 -490
- package/dist/presets/public-gallery.js +2 -1
- package/dist/presets/public-handoff.d.ts +2 -68
- package/dist/presets/public-handoff.js +110 -24
- package/dist/presets/public-preview-artifacts.d.ts +2 -2
- package/dist/presets/public-preview.d.ts +2 -1168
- package/dist/presets/public-preview.js +17 -17
- package/dist/presets/sources.js +17 -17
- package/dist/public-gallery-D0Zv0pZE.d.ts +611 -0
- package/dist/public-preview-u1Z9aaGq.d.ts +1168 -0
- package/dist/render-template.d.ts +2 -2
- package/dist/render-template.js +1 -1
- 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-email-language-reference.md +1 -1
- package/generated/sml-language-reference.md +1 -1
- package/package.json +6 -3
- package/dist/ast.js.map +0 -1
- package/dist/candidate.js.map +0 -1
- package/dist/checks-CW0nFj5n.js.map +0 -1
- package/dist/classes.js.map +0 -1
- package/dist/compile-sms.js.map +0 -1
- package/dist/compile-whatsapp.js.map +0 -1
- package/dist/compile.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/conditionals.js.map +0 -1
- package/dist/diagnostics.js.map +0 -1
- package/dist/emit.js.map +0 -1
- package/dist/footer.js.map +0 -1
- package/dist/interpolate.js.map +0 -1
- package/dist/jsx-dev-runtime.js.map +0 -1
- package/dist/jsx-runtime.js.map +0 -1
- package/dist/lexer.js.map +0 -1
- package/dist/model.js.map +0 -1
- package/dist/ops.js.map +0 -1
- package/dist/parser.js.map +0 -1
- package/dist/predicate.js.map +0 -1
- package/dist/presets/category-mapping.js.map +0 -1
- package/dist/presets/public-gallery.js.map +0 -1
- package/dist/presets/public-handoff.js.map +0 -1
- package/dist/presets/public-preview-artifacts.js.map +0 -1
- package/dist/presets/public-preview.js.map +0 -1
- package/dist/presets/sources.js.map +0 -1
- package/dist/presets.js.map +0 -1
- package/dist/printer.js.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/render-template.js.map +0 -1
- package/dist/sms-segments.js.map +0 -1
- package/dist/theme-css.js.map +0 -1
- package/dist/theme.js.map +0 -1
- package/dist/tokens.js.map +0 -1
- package/dist/variables.js.map +0 -1
- package/dist/version-BITr58kk.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { collectVariableReferences } from "../ast.js";
|
|
2
2
|
import { parseSml } from "../parser.js";
|
|
3
|
+
import { resolveTokenTable } from "../theme.js";
|
|
4
|
+
import { baseTokens } from "../tokens.js";
|
|
3
5
|
import { compileSource } from "../compile.js";
|
|
4
6
|
import { RECOMMENDED_THEME, presetCatalog } from "../presets.js";
|
|
5
7
|
import { publicTemplates, templateCollections } from "./public-gallery.js";
|
|
@@ -7,6 +9,18 @@ import { Result } from "effect";
|
|
|
7
9
|
//#region presets/public-handoff.ts
|
|
8
10
|
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
11
|
const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
12
|
+
const MAX_HTTPS_ASSET_LENGTH = 2048;
|
|
13
|
+
const MAX_DATA_IMAGE_ASSET_LENGTH = 32768;
|
|
14
|
+
const DATA_IMAGE = /^data:image\/(?:avif|gif|jpeg|png|svg\+xml|webp)(?:;charset=[a-z0-9-]+)?(?:;base64)?,[^\r\n]+$/i;
|
|
15
|
+
const isTemplateReferenceProvenanceSource = (value) => value === "sanitized-synthetic" || value === "reviewed-public";
|
|
16
|
+
const PUBLIC_TEMPLATE_FONT_TOKENS = new Set(Object.keys(resolveTokenTable({
|
|
17
|
+
base: baseTokens,
|
|
18
|
+
overrides: [RECOMMENDED_THEME]
|
|
19
|
+
}).fontFamilies));
|
|
20
|
+
const FONT_WEIGHT_TOKENS = new Set(Object.keys(resolveTokenTable({
|
|
21
|
+
base: baseTokens,
|
|
22
|
+
overrides: [RECOMMENDED_THEME]
|
|
23
|
+
}).fontWeights));
|
|
10
24
|
const sortedUnique = (values) => [...new Set(values)].sort();
|
|
11
25
|
const sameStrings = (left, right) => JSON.stringify(sortedUnique(left)) === JSON.stringify(sortedUnique(right));
|
|
12
26
|
const nonEmpty = (value) => value.trim().length > 0;
|
|
@@ -16,6 +30,75 @@ const issue = (artifactId, code, message) => ({
|
|
|
16
30
|
message
|
|
17
31
|
});
|
|
18
32
|
/**
|
|
33
|
+
* Public imports may only refer to existing font-family tokens. A source may
|
|
34
|
+
* still use the supported `font-*` weight utilities; this check deliberately
|
|
35
|
+
* rejects a new family token instead of creating a corpus font-hosting path.
|
|
36
|
+
*/
|
|
37
|
+
const unsupportedPublicTemplateFontTokens = (source) => {
|
|
38
|
+
const unsupported = /* @__PURE__ */ new Set();
|
|
39
|
+
for (const match of source.matchAll(/\bclass\s*=\s*(["'])(.*?)\1/gs)) for (const className of (match[2] ?? "").split(/\s+/)) {
|
|
40
|
+
if (!className.startsWith("font-")) continue;
|
|
41
|
+
const token = className.slice(5);
|
|
42
|
+
if (!PUBLIC_TEMPLATE_FONT_TOKENS.has(token) && !FONT_WEIGHT_TOKENS.has(token)) unsupported.add(token);
|
|
43
|
+
}
|
|
44
|
+
return [...unsupported].sort();
|
|
45
|
+
};
|
|
46
|
+
/** Accept only bounded HTTPS URLs or the preview's self-contained image data. */
|
|
47
|
+
const isSafePublicTemplateAsset = (value) => {
|
|
48
|
+
if (value.startsWith("data:")) return value.length <= MAX_DATA_IMAGE_ASSET_LENGTH && DATA_IMAGE.test(value);
|
|
49
|
+
const url = URL.parse(value);
|
|
50
|
+
return value.length <= MAX_HTTPS_ASSET_LENGTH && url?.protocol === "https:" && url.hostname.length > 0 && url.username.length === 0 && url.password.length === 0;
|
|
51
|
+
};
|
|
52
|
+
/** Normalizes one approved handoff for gallery and future agent consumers. */
|
|
53
|
+
const toTemplateReferencePacket = (artifact) => ({
|
|
54
|
+
version: 1,
|
|
55
|
+
preset: artifact.preset,
|
|
56
|
+
gallery: {
|
|
57
|
+
...artifact.gallery,
|
|
58
|
+
path: `/templates/${artifact.gallery.slug}`
|
|
59
|
+
},
|
|
60
|
+
preview: artifact.preview,
|
|
61
|
+
publicMetadata: artifact.publicMetadata,
|
|
62
|
+
...artifact.provenance === void 0 ? {} : { provenance: artifact.provenance }
|
|
63
|
+
});
|
|
64
|
+
const jsonProjection = (kind, value) => ({
|
|
65
|
+
kind,
|
|
66
|
+
relativePath: `${kind}.json`,
|
|
67
|
+
contents: `${JSON.stringify(value, null, 2)}\n`
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Emits the five destination projections as deterministic bytes. The importer
|
|
71
|
+
* owns no checked-in destination files; callers choose an explicit output dir.
|
|
72
|
+
*/
|
|
73
|
+
const emitTemplateReferencePacket = (packet) => [
|
|
74
|
+
{
|
|
75
|
+
kind: "source",
|
|
76
|
+
relativePath: "preset.samva",
|
|
77
|
+
contents: packet.preset.source
|
|
78
|
+
},
|
|
79
|
+
jsonProjection("catalog", {
|
|
80
|
+
id: packet.preset.id,
|
|
81
|
+
channel: packet.preset.channel,
|
|
82
|
+
category: packet.preset.category,
|
|
83
|
+
name: packet.preset.name,
|
|
84
|
+
description: packet.preset.description,
|
|
85
|
+
variables: packet.preset.variables
|
|
86
|
+
}),
|
|
87
|
+
jsonProjection("gallery", {
|
|
88
|
+
presetId: packet.preset.id,
|
|
89
|
+
...packet.gallery,
|
|
90
|
+
...packet.provenance === void 0 ? {} : { provenance: packet.provenance }
|
|
91
|
+
}),
|
|
92
|
+
jsonProjection("preview", packet.preview),
|
|
93
|
+
jsonProjection("editorial", {
|
|
94
|
+
cardTitle: packet.publicMetadata.cardTitle,
|
|
95
|
+
cardDescription: packet.publicMetadata.cardDescription,
|
|
96
|
+
guidance: packet.publicMetadata.guidance,
|
|
97
|
+
variables: packet.publicMetadata.variableDescriptions,
|
|
98
|
+
seo: packet.publicMetadata.seo
|
|
99
|
+
})
|
|
100
|
+
];
|
|
101
|
+
/**
|
|
19
102
|
* Validates explicitly supplied, owner-approved artifacts without filesystem,
|
|
20
103
|
* network, persistence, or discovery. Successful output is a projection for
|
|
21
104
|
* the existing preset, public-gallery, preview, and editorial contracts; this
|
|
@@ -32,6 +115,7 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
32
115
|
for (const artifact of artifacts) {
|
|
33
116
|
const id = artifact.preset.id;
|
|
34
117
|
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"));
|
|
118
|
+
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
119
|
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
120
|
if (existingIds.has(id) || suppliedIds.has(id)) issues.push(issue(id, "duplicate-id", `preset id "${id}" is already present`));
|
|
37
121
|
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`));
|
|
@@ -40,6 +124,8 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
40
124
|
const compiled = compileSource(artifact.preset.source, { theme: { overrides: [RECOMMENDED_THEME] } });
|
|
41
125
|
if (Result.isFailure(compiled)) issues.push(issue(id, "source", compiled.failure.message));
|
|
42
126
|
else if (artifact.preset.channel !== "email" || compiled.success.channel !== "email") issues.push(issue(id, "channel", "public template source must compile as email"));
|
|
127
|
+
const unsupportedFontTokens = unsupportedPublicTemplateFontTokens(artifact.preset.source);
|
|
128
|
+
if (unsupportedFontTokens.length > 0) issues.push(issue(id, "font-token", `unsupported public font token: ${unsupportedFontTokens.join(", ")}`));
|
|
43
129
|
const parsed = parseSml(artifact.preset.source);
|
|
44
130
|
if (Result.isSuccess(parsed)) {
|
|
45
131
|
const sourceVariables = [...collectVariableReferences(parsed.success)];
|
|
@@ -52,9 +138,8 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
52
138
|
if (new Set(assetVariables).size !== assetVariables.length) issues.push(issue(id, "assets", "asset variable references must be unique"));
|
|
53
139
|
for (const asset of artifact.preview.assets) {
|
|
54
140
|
const value = artifact.preview.sampleData[asset.variable];
|
|
55
|
-
const url = value === void 0 ? null : URL.parse(value);
|
|
56
141
|
if (!artifact.preset.variables.includes(asset.variable)) issues.push(issue(id, "assets", `asset "${asset.variable}" is not a declared preset variable`));
|
|
57
|
-
if (
|
|
142
|
+
if (value === void 0 || !isSafePublicTemplateAsset(value)) issues.push(issue(id, "assets", `asset "${asset.variable}" must reference a bounded HTTPS or data:image sample URL`));
|
|
58
143
|
}
|
|
59
144
|
const metadata = artifact.publicMetadata;
|
|
60
145
|
const metadataStrings = [
|
|
@@ -77,29 +162,30 @@ const validatePublicTemplateHandoff = (artifacts) => {
|
|
|
77
162
|
for (const relatedId of artifact.gallery.relatedTemplates) if (relatedId === id || !allKnownIds.has(relatedId)) issues.push(issue(id, "reference", `related template "${relatedId}" is invalid`));
|
|
78
163
|
}
|
|
79
164
|
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
|
-
|
|
165
|
+
return Result.succeed(artifacts.map((artifact) => {
|
|
166
|
+
const reference = toTemplateReferencePacket(artifact);
|
|
167
|
+
return {
|
|
168
|
+
approval: {
|
|
169
|
+
...artifact.approval,
|
|
170
|
+
state: "approved"
|
|
171
|
+
},
|
|
172
|
+
preset: {
|
|
173
|
+
id: artifact.preset.id,
|
|
174
|
+
channel: artifact.preset.channel,
|
|
175
|
+
category: artifact.preset.category,
|
|
176
|
+
name: artifact.preset.name,
|
|
177
|
+
description: artifact.preset.description,
|
|
178
|
+
variables: artifact.preset.variables
|
|
179
|
+
},
|
|
180
|
+
source: artifact.preset.source,
|
|
181
|
+
gallery: reference.gallery,
|
|
182
|
+
preview: artifact.preview,
|
|
183
|
+
publicMetadata: artifact.publicMetadata,
|
|
184
|
+
reference
|
|
185
|
+
};
|
|
186
|
+
}));
|
|
101
187
|
};
|
|
102
188
|
//#endregion
|
|
103
|
-
export { validatePublicTemplateHandoff };
|
|
189
|
+
export { emitTemplateReferencePacket, isSafePublicTemplateAsset, toTemplateReferencePacket, unsupportedPublicTemplateFontTokens, validatePublicTemplateHandoff };
|
|
104
190
|
|
|
105
191
|
//# sourceMappingURL=public-handoff.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EmailPresetId } from "../presets.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { n as PreviewQuality } from "../public-preview-u1Z9aaGq.js";
|
|
3
|
+
import { t as PublicTemplate } from "../public-gallery-D0Zv0pZE.js";
|
|
4
4
|
import { Result } from "effect";
|
|
5
5
|
//#region presets/public-preview-artifacts.d.ts
|
|
6
6
|
type PreviewSurface = "card" | "detail" | "og";
|