@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.
Files changed (74) hide show
  1. package/dist/ast.js +1 -1
  2. package/dist/{checks-CW0nFj5n.js → checks-D-zFQt5R.js} +2 -2
  3. package/dist/checks.js +1 -1
  4. package/dist/compile-sms.d.ts +1 -1
  5. package/dist/compile-whatsapp.d.ts +1 -1
  6. package/dist/components.d.ts +3 -3
  7. package/dist/conditionals.js +1 -1
  8. package/dist/diagnostics.js +2 -2
  9. package/dist/jsx-dev-runtime.d.ts +1 -1
  10. package/dist/{jsx-runtime-fSxcvlx4.d.ts → jsx-runtime-eNIMY87F.d.ts} +2 -2
  11. package/dist/jsx-runtime.d.ts +1 -1
  12. package/dist/ops.js +2 -2
  13. package/dist/{parser-yzjUOGhc.d.ts → parser-DkQKIjDa.d.ts} +11 -2
  14. package/dist/parser.d.ts +2 -2
  15. package/dist/parser.js +48 -9
  16. package/dist/predicate-CvkzT-a2.js +539 -0
  17. package/dist/{predicate-DTtVtLK7.d.ts → predicate-G2SeSvoN.d.ts} +9 -2
  18. package/dist/predicate.d.ts +2 -2
  19. package/dist/predicate.js +2 -417
  20. package/dist/presets/category-mapping.d.ts +1 -1
  21. package/dist/presets/public-gallery.d.ts +2 -490
  22. package/dist/presets/public-gallery.js +2 -1
  23. package/dist/presets/public-handoff.d.ts +2 -68
  24. package/dist/presets/public-handoff.js +110 -24
  25. package/dist/presets/public-preview-artifacts.d.ts +2 -2
  26. package/dist/presets/public-preview.d.ts +2 -1168
  27. package/dist/presets/public-preview.js +17 -17
  28. package/dist/presets/sources.js +17 -17
  29. package/dist/public-gallery-D0Zv0pZE.d.ts +611 -0
  30. package/dist/public-preview-u1Z9aaGq.d.ts +1168 -0
  31. package/dist/render-template.d.ts +2 -2
  32. package/dist/render-template.js +1 -1
  33. package/dist/{variables-3NGLpNTB.d.ts → variables-if1VrV34.d.ts} +2 -2
  34. package/dist/variables.d.ts +1 -1
  35. package/dist/variables.js +1 -1
  36. package/generated/sml-email-language-reference.md +1 -1
  37. package/generated/sml-language-reference.md +1 -1
  38. package/package.json +6 -3
  39. package/dist/ast.js.map +0 -1
  40. package/dist/candidate.js.map +0 -1
  41. package/dist/checks-CW0nFj5n.js.map +0 -1
  42. package/dist/classes.js.map +0 -1
  43. package/dist/compile-sms.js.map +0 -1
  44. package/dist/compile-whatsapp.js.map +0 -1
  45. package/dist/compile.js.map +0 -1
  46. package/dist/components.js.map +0 -1
  47. package/dist/conditionals.js.map +0 -1
  48. package/dist/diagnostics.js.map +0 -1
  49. package/dist/emit.js.map +0 -1
  50. package/dist/footer.js.map +0 -1
  51. package/dist/interpolate.js.map +0 -1
  52. package/dist/jsx-dev-runtime.js.map +0 -1
  53. package/dist/jsx-runtime.js.map +0 -1
  54. package/dist/lexer.js.map +0 -1
  55. package/dist/model.js.map +0 -1
  56. package/dist/ops.js.map +0 -1
  57. package/dist/parser.js.map +0 -1
  58. package/dist/predicate.js.map +0 -1
  59. package/dist/presets/category-mapping.js.map +0 -1
  60. package/dist/presets/public-gallery.js.map +0 -1
  61. package/dist/presets/public-handoff.js.map +0 -1
  62. package/dist/presets/public-preview-artifacts.js.map +0 -1
  63. package/dist/presets/public-preview.js.map +0 -1
  64. package/dist/presets/sources.js.map +0 -1
  65. package/dist/presets.js.map +0 -1
  66. package/dist/printer.js.map +0 -1
  67. package/dist/registry.js.map +0 -1
  68. package/dist/render-template.js.map +0 -1
  69. package/dist/sms-segments.js.map +0 -1
  70. package/dist/theme-css.js.map +0 -1
  71. package/dist/theme.js.map +0 -1
  72. package/dist/tokens.js.map +0 -1
  73. package/dist/variables.js.map +0 -1
  74. 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 (url === null || url.protocol !== "https:") issues.push(issue(id, "assets", `asset "${asset.variable}" must reference a valid HTTPS sample URL`));
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
- approval: {
82
- ...artifact.approval,
83
- state: "approved"
84
- },
85
- preset: {
86
- id: artifact.preset.id,
87
- channel: artifact.preset.channel,
88
- category: artifact.preset.category,
89
- name: artifact.preset.name,
90
- description: artifact.preset.description,
91
- variables: artifact.preset.variables
92
- },
93
- source: artifact.preset.source,
94
- gallery: {
95
- ...artifact.gallery,
96
- path: `/templates/${artifact.gallery.slug}`
97
- },
98
- preview: artifact.preview,
99
- publicMetadata: artifact.publicMetadata
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 { PublicTemplate } from "./public-gallery.js";
3
- import { PreviewQuality } from "./public-preview.js";
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";