@templatical/template-tools 0.47.0 → 0.48.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/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as buildSpecimenTemplate, c as runRecipe, d as schema, f as validateTemplate, i as buildSpecimen, l as validateCustomBlockDefinition, n as renderSpecimenMjml, r as SPECIMEN_STATES, s as checkRecipe, t as checkCustomBlock, u as runQualityLint } from "./custom-block-sAM61QPU.js";
3
- import { t as applyOperation } from "./src-Ce1Oh8m_.js";
2
+ import { a as buildSpecimenTemplate, c as runRecipe, d as schema, f as validateTemplate, i as buildSpecimen, l as validateCustomBlockDefinition, n as renderSpecimenMjml, r as SPECIMEN_STATES, s as checkRecipe, t as checkCustomBlock, u as runQualityLint } from "./custom-block-CsZN9qMM.js";
3
+ import { t as applyOperation } from "./src-SZZkttBC.js";
4
4
  import { DEFAULT_PORT, WORKING_DIR, listWorkingFiles, openBrowser, pidfilePath, processAlive, readPidfile, readWorkingFile, startBridgePreferring } from "./live/index.js";
5
5
  import { renderToMjml } from "@templatical/renderer";
6
6
  import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
@@ -92,6 +92,10 @@ const schema = {
92
92
  "$ref": "#/definitions/BackgroundImage",
93
93
  "description": "Image behind the section's columns, rendered as `background-url` (plus size, position and repeat) on the `mj-section`. `styles.backgroundColor` is its fallback."
94
94
  },
95
+ "hero": {
96
+ "$ref": "#/definitions/SectionHero",
97
+ "description": "Draws a one-column section with a background image as an `mj-hero`: the image fills the box and the content sits on top. Ignored (the section renders as a regular one, and the `structure` linter says why) with more than one column, without an image, or with `height: \"image\"` and no image size."
98
+ },
95
99
  "wrapper": {
96
100
  "$ref": "#/definitions/SectionWrapper",
97
101
  "description": "Optional outer frame (rendered as an `mj-wrapper` around the section)."
@@ -231,6 +235,14 @@ const schema = {
231
235
  "repeat": {
232
236
  "type": "boolean",
233
237
  "description": "`false` paints the image once; `true` tiles it."
238
+ },
239
+ "naturalWidth": {
240
+ "type": "number",
241
+ "description": "The image file's own width in px, when known: given by the media library, measured by the editor when the author sets the URL, or read by an importer (from MJML, the hero's `background-width`, which may be the box's size rather than the file's). Only the ratio with `naturalHeight` is used: a section hero with `height: \"image\"` keeps its box at that shape. Both or neither; one without the other reads as unknown."
242
+ },
243
+ "naturalHeight": {
244
+ "type": "number",
245
+ "description": "The image file's own height in px, when known; it pairs with `naturalWidth`, and only their ratio is used. Both or neither."
234
246
  }
235
247
  },
236
248
  "required": [
@@ -267,6 +279,31 @@ const schema = {
267
279
  "bottom"
268
280
  ]
269
281
  },
282
+ "SectionHero": {
283
+ "type": "object",
284
+ "properties": {
285
+ "height": {
286
+ "anyOf": [{
287
+ "type": "string",
288
+ "const": "image"
289
+ }, { "type": "number" }],
290
+ "description": "`\"image\"` makes the box as tall as the image drawn across its desktop width, and needs the image's size on `backgroundImage.naturalWidth` / `naturalHeight`; a phone keeps that height and crops the image's sides. A number is the box's fixed height in px, padding included."
291
+ },
292
+ "verticalAlign": { "$ref": "#/definitions/HeroVerticalAlign" }
293
+ },
294
+ "required": ["height", "verticalAlign"],
295
+ "additionalProperties": false,
296
+ "description": "Draws a one-column section as an MJML `mj-hero`: its background image fills the box (always `cover`, never tiled) and the content sits on top of it. A hero applies only to a one-column section with a background image; otherwise the section renders as a regular one."
297
+ },
298
+ "HeroVerticalAlign": {
299
+ "type": "string",
300
+ "enum": [
301
+ "top",
302
+ "middle",
303
+ "bottom"
304
+ ],
305
+ "description": "Where a hero's content sits in its box."
306
+ },
270
307
  "SectionWrapper": {
271
308
  "type": "object",
272
309
  "properties": {
@@ -421,6 +458,11 @@ const schema = {
421
458
  "type": "number",
422
459
  "description": "Height in pixels. Absent means the height is derived from the width, so the image keeps its aspect ratio — setting both stretches it, since email clients don't support `object-fit`."
423
460
  },
461
+ "naturalWidth": {
462
+ "type": "number",
463
+ "description": "The image file's own size in px, when known: given by the media library, read by an importer, or measured in the editor during an edit. Both or neither; one without the other reads as unknown. It drives the editor's aspect-ratio lock and is never rendered. Cleared when `src` changes without a new size alongside it."
464
+ },
465
+ "naturalHeight": { "type": "number" },
424
466
  "align": {
425
467
  "type": "string",
426
468
  "enum": [
@@ -2625,4 +2667,4 @@ async function checkCustomBlock(data, { compileMjml } = {}) {
2625
2667
  //#endregion
2626
2668
  export { buildSpecimenTemplate as a, runRecipe as c, schema as d, validateTemplate as f, buildSpecimen as i, validateCustomBlockDefinition as l, renderSpecimenMjml as n, fetchedSpecimenBlocks as o, SPECIMEN_STATES as r, checkRecipe as s, checkCustomBlock as t, runQualityLint as u };
2627
2669
 
2628
- //# sourceMappingURL=custom-block-sAM61QPU.js.map
2670
+ //# sourceMappingURL=custom-block-CsZN9qMM.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"custom-block-sAM61QPU.js","names":["rawSchema","rawSchema"],"sources":["../schema.json","../src/validate.ts","../src/custom-block/safety.ts","../src/custom-block/a11y.ts","../custom-block-schema.json","../src/custom-block/definition.ts","../src/custom-block/liquid.ts","../src/custom-block/recipe.ts","../src/custom-block/specimen.ts","../src/custom-block/render.ts","../src/custom-block/check.ts"],"sourcesContent":["","// Structural validation of a Templatical template against the generated JSON\n// Schema, plus the @templatical/quality lint layered on top.\n//\n// Structural validation is discriminator-aware: each block is checked against\n// the subschema for its declared `type` (e.g. a block with `\"type\": \"button\"`\n// is validated as a ButtonBlock), so errors are precise (\"blocks[2] (button)\n// must have required property 'url'\") instead of the raw anyOf's \"must match\n// exactly one schema in anyOf\".\n\nimport Ajv, { type ValidateFunction, type ErrorObject } from \"ajv\";\nimport { lintTemplate, type LintIssue } from \"@templatical/quality\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport rawSchema from \"../schema.json\";\n\ninterface SchemaDefinition {\n properties?: { type?: { const?: unknown } } & Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface SchemaDocument {\n definitions: Record<string, SchemaDefinition>;\n [key: string]: unknown;\n}\n\n/** The generated JSON Schema for `TemplateContent`, as a plain object. */\nexport const schema = rawSchema as unknown as SchemaDocument;\n\nconst ajv = new Ajv({ allErrors: true, strict: false });\n\n// Map a block's `type` discriminator (e.g. \"button\", \"social\") to its schema\n// definition name (e.g. \"ButtonBlock\", \"SocialIconsBlock\"), derived from the\n// schema itself so it never drifts from the generated defs.\nconst typeToDef: Record<string, string> = {};\nfor (const [name, def] of Object.entries(schema.definitions)) {\n const constType = def?.properties?.type?.const;\n if (typeof constType === \"string\") {\n typeToDef[constType] = name;\n }\n}\n\nconst validatorCache = new Map<string, ValidateFunction>();\n\nfunction validatorFor(defName: string): ValidateFunction {\n const cached = validatorCache.get(defName);\n if (cached) return cached;\n\n const definitions = structuredClone(schema.definitions);\n // A section's `children` holds nested blocks (Block[][]); we recurse into\n // them separately for precise paths, so stub the deep check here.\n if (defName === \"SectionBlock\") {\n const section = definitions.SectionBlock;\n if (section?.properties) {\n section.properties.children = { type: \"array\" };\n }\n }\n const compiled = ajv.compile({\n $ref: `#/definitions/${defName}`,\n definitions,\n });\n validatorCache.set(defName, compiled);\n return compiled;\n}\n\nconst settingsValidator = ajv.compile({\n $ref: \"#/definitions/TemplateSettings\",\n definitions: structuredClone(schema.definitions),\n});\n\ninterface FlatBlock {\n path: string;\n block: Record<string, unknown> | null;\n}\n\nfunction flattenBlocks(\n blocks: unknown,\n basePath: string,\n out: FlatBlock[],\n): void {\n if (!Array.isArray(blocks)) return;\n blocks.forEach((block, i) => {\n const path = `${basePath}[${i}]`;\n out.push({ path, block: block ?? null });\n if (block?.type === \"section\" && Array.isArray(block.children)) {\n block.children.forEach((column: unknown, ci: number) => {\n flattenBlocks(column, `${path}.children[${ci}]`, out);\n });\n }\n });\n}\n\nfunction formatAjvErrors(\n errors: ErrorObject[] | null | undefined,\n prefix: string,\n): string[] {\n return (errors ?? []).map((e) => {\n const where = `${prefix}${e.instancePath}`;\n const extra =\n e.keyword === \"additionalProperties\"\n ? ` (${(e.params as { additionalProperty?: string }).additionalProperty})`\n : \"\";\n return `${where} ${e.message}${extra}`;\n });\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Structural validation. Synchronous, depends only on ajv + the committed\n * schema.json (no build of the workspace packages required).\n */\nexport function validateTemplate(data: unknown): ValidationResult {\n const errors: string[] = [];\n\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return { valid: false, errors: [\"(root) must be an object\"] };\n }\n\n const doc = data as Record<string, unknown>;\n\n if (!Array.isArray(doc.blocks)) {\n errors.push(\"blocks must be an array\");\n }\n if (doc.settings === null || typeof doc.settings !== \"object\") {\n errors.push(\"settings must be an object\");\n } else if (!settingsValidator(doc.settings)) {\n errors.push(...formatAjvErrors(settingsValidator.errors, \"settings\"));\n }\n\n const flat: FlatBlock[] = [];\n flattenBlocks(doc.blocks, \"blocks\", flat);\n for (const { path, block } of flat) {\n const type = block?.type;\n const defName = typeof type === \"string\" ? typeToDef[type] : undefined;\n if (!defName) {\n const known = Object.keys(typeToDef).join(\", \");\n errors.push(\n `${path} has unknown or missing block type ${JSON.stringify(type)} (expected one of: ${known})`,\n );\n continue;\n }\n const validate = validatorFor(defName);\n if (!validate(block)) {\n errors.push(...formatAjvErrors(validate.errors, `${path} (${type})`));\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\nexport interface QualityLintResult {\n issues: LintIssue[];\n /** Set when the linter itself threw — the template is structurally suspect. */\n error?: string;\n}\n\n/**\n * Quality layer — accessibility / structure / link linting.\n *\n * Assumes structurally-valid input, so callers should run `validateTemplate`\n * first; the try/catch is a guard against a malformed template crashing the\n * linter rather than a substitute for that ordering.\n */\nexport function runQualityLint(data: unknown): QualityLintResult {\n try {\n return { issues: lintTemplate(data as TemplateContent) ?? [] };\n } catch (err) {\n return { issues: [], error: (err as Error).message };\n }\n}\n","// Email-client safety over a custom block's rendered HTML. Custom blocks are\n// raw HTML the SDK passes through untouched (inside <mj-text>), so nothing\n// downstream repairs a layout Outlook can't draw; this is the only check.\n// Rules run on every specimen state's output so both sides of each {% if %}\n// are seen, and an empty optional image surfaces as src=\"\".\n\nimport { Parser } from \"htmlparser2\";\nimport type { RenderedState } from \"./render\";\nimport type { CustomBlockIssue, CustomBlockWorkingFile } from \"./types\";\n\nconst FLEX_GRID = /(^|;)\\s*display\\s*:\\s*(inline-)?(flex|grid)\\b/i;\nconst POSITION = /(^|;)\\s*position\\s*:\\s*(absolute|fixed)\\b/i;\nconst DIV_LAYOUT = /(^|;)\\s*(width|max-width|float)\\s*:/i;\n// `var(--x)` anywhere: Gmail and Outlook drop custom properties, and with them\n// the whole declaration that reads one.\nconst CSS_VARIABLE = /\\bvar\\(\\s*--/i;\n// A colour value in a `background:` shorthand: hex, or an rgb()/hsl() call.\nconst COLOR_VALUE = /#[0-9a-f]{3,8}\\b|\\b(?:rgba?|hsla?)\\(/i;\nconst FORM_CONTROLS = new Set([\"form\", \"input\", \"select\", \"textarea\"]);\n\n// The same two layout rules over stylesheet text, where a declaration can\n// also follow `{` or a newline.\nconst CSS_FLEX_GRID = /(^|[;{\\s])display\\s*:\\s*(inline-)?(flex|grid)\\b/i;\nconst CSS_POSITION = /(^|[;{\\s])position\\s*:\\s*(absolute|fixed)\\b/i;\n\nconst IMPORT_MESSAGE =\n \"`@import` is dropped by most email clients; inline the rules.\";\nconst FLEX_GRID_MESSAGE =\n \"`display: flex`/`grid` is ignored by Outlook and Gmail on many clients; use tables.\";\nconst POSITION_MESSAGE =\n \"`position: absolute/fixed` is unsupported in email clients.\";\nconst CSS_VARIABLE_MESSAGE =\n \"CSS custom properties (`var(--…)`) are dropped by Gmail and Outlook, and the whole declaration with them; write the value itself.\";\n\n/** A style attribute's declarations as lowercase property → raw value pairs. */\nexport function declarations(style: string): [string, string][] {\n const out: [string, string][] = [];\n for (const part of style.split(\";\")) {\n const colon = part.indexOf(\":\");\n if (colon > 0) {\n out.push([\n part.slice(0, colon).trim().toLowerCase(),\n part.slice(colon + 1),\n ]);\n }\n }\n return out;\n}\n\n/**\n * Whether a background image here has a colour to fall back on when images\n * are blocked: `background-color`, a colour inside the `background:`\n * shorthand, or the `bgcolor` attribute Outlook reads.\n */\nfunction backgroundIssue(\n style: string,\n attrs: Record<string, string>,\n): boolean {\n const decls = declarations(style);\n const hasImage = decls.some(\n ([prop, value]) =>\n (prop === \"background\" || prop === \"background-image\") &&\n /url\\(/i.test(value),\n );\n if (!hasImage || \"bgcolor\" in attrs) return false;\n return !decls.some(\n ([prop, value]) =>\n prop === \"background-color\" ||\n (prop === \"background\" &&\n COLOR_VALUE.test(stripCssUrlsAndStrings(value))),\n );\n}\n\n/** Drops `/* … *\\/` comments in one linear pass; an unterminated comment drops the rest. */\nexport function stripCssComments(css: string): string {\n let out = \"\";\n let i = 0;\n for (;;) {\n const open = css.indexOf(\"/*\", i);\n if (open === -1) return out + css.slice(i);\n out += css.slice(i, open);\n const close = css.indexOf(\"*/\", open + 2);\n if (close === -1) return out;\n i = close + 2;\n }\n}\n\n/**\n * Remove `url(...)` and quoted strings so the class scan can't read `.png` or\n * `\"x.y\"` as a selector. Linear: each opener's next position is searched for\n * again only once the cursor has passed it, so an opener that never closes\n * costs one scan, not one per occurrence. An unterminated `url(` or quote\n * drops the rest, as a CSS parser would.\n */\nexport function stripCssUrlsAndStrings(css: string): string {\n const lower = css.toLowerCase();\n const next = (needle: string, from: number) => {\n const at = lower.indexOf(needle, from);\n return at === -1 ? Infinity : at;\n };\n const closers: Record<string, string> = { \"url(\": \")\", '\"': '\"', \"'\": \"'\" };\n const pos: Record<string, number> = { \"url(\": 0, '\"': 0, \"'\": 0 };\n for (const k of Object.keys(pos)) pos[k] = next(k, 0);\n let out = \"\";\n let i = 0;\n for (;;) {\n let opener = \"\";\n let open = Infinity;\n for (const k of Object.keys(pos)) {\n if (pos[k] < i) pos[k] = next(k, i);\n if (pos[k] < open) {\n open = pos[k];\n opener = k;\n }\n }\n if (open === Infinity) return out + css.slice(i);\n out += css.slice(i, open);\n const close = lower.indexOf(closers[opener], open + opener.length);\n if (close === -1) return out;\n i = close + 1;\n }\n}\n\n/** Layout rules that hold for stylesheet text: the definition's and a `<style>`'s. */\nfunction cssLayoutIssues(css: string, path?: string): CustomBlockIssue[] {\n const text = stripCssComments(css);\n const issues: CustomBlockIssue[] = [];\n const at = path === undefined ? {} : { path };\n if (CSS_FLEX_GRID.test(text))\n issues.push({\n ruleId: \"safety.flex-grid\",\n severity: \"error\",\n ...at,\n message: FLEX_GRID_MESSAGE,\n });\n if (CSS_POSITION.test(text))\n issues.push({\n ruleId: \"safety.position\",\n severity: \"error\",\n ...at,\n message: POSITION_MESSAGE,\n });\n if (CSS_VARIABLE.test(text))\n issues.push({\n ruleId: \"safety.css-variable\",\n severity: \"warning\",\n ...at,\n message: CSS_VARIABLE_MESSAGE,\n });\n return issues;\n}\n\nfunction stylesheetIssues(\n def: CustomBlockWorkingFile,\n css: string,\n): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n if (/@import\\b/i.test(stripCssComments(css))) {\n issues.push({\n ruleId: \"safety.import\",\n severity: \"error\",\n path: \"/stylesheet\",\n message: IMPORT_MESSAGE,\n });\n }\n issues.push(...cssLayoutIssues(css, \"/stylesheet\"));\n const stripped = stripCssUrlsAndStrings(stripCssComments(css));\n const prefix = `tplc-${def.type}-`;\n const unscoped = [\n ...new Set(\n [...stripped.matchAll(/\\.(-?[_a-zA-Z][\\w-]*)/g)].map((m) => m[1]),\n ),\n ].filter((cls) => !cls.startsWith(prefix));\n if (unscoped.length > 0) {\n issues.push({\n ruleId: \"safety.unscoped-class\",\n severity: \"warning\",\n path: \"/stylesheet\",\n message: `Classes ${unscoped.map((c) => `.${c}`).join(\", \")} aren't prefixed \\`${prefix}\\`; the SDK doesn't scope them, so they can collide with other blocks.`,\n });\n }\n return issues;\n}\n\nexport function checkEmailSafety(\n def: CustomBlockWorkingFile,\n rendered: RenderedState[],\n): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n const add = (\n ruleId: string,\n severity: CustomBlockIssue[\"severity\"],\n message: string,\n ) => issues.push({ ruleId, severity, message });\n\n for (const { state, html } of rendered) {\n let inStyle = false;\n let styleText = \"\";\n const parser = new Parser(\n {\n onopentag(name, attrs) {\n const style = attrs.style ?? \"\";\n if (FLEX_GRID.test(style))\n add(\"safety.flex-grid\", \"error\", FLEX_GRID_MESSAGE);\n if (POSITION.test(style))\n add(\"safety.position\", \"error\", POSITION_MESSAGE);\n if (CSS_VARIABLE.test(style))\n add(\"safety.css-variable\", \"warning\", CSS_VARIABLE_MESSAGE);\n if (backgroundIssue(style, attrs))\n add(\n \"safety.bg-image-fallback\",\n \"warning\",\n \"A background image needs a fallback colour (`background-color`, a colour in `background:`, or `bgcolor`); many clients block images.\",\n );\n if (name === \"svg\")\n add(\n \"safety.svg\",\n \"error\",\n \"Inline `<svg>` is stripped by Gmail and not drawn by Outlook; use a PNG `<img>`.\",\n );\n if (FORM_CONTROLS.has(name))\n add(\n \"safety.form\",\n \"warning\",\n `\\`<${name}>\\` doesn't work in most email clients (Gmail and Outlook disable forms); link to a page instead.`,\n );\n if (name === \"script\")\n add(\n \"safety.script\",\n \"error\",\n \"`<script>` is stripped by every email client.\",\n );\n if (\n name === \"link\" &&\n (attrs.rel ?? \"\").toLowerCase() === \"stylesheet\"\n )\n add(\n \"safety.external-stylesheet\",\n \"error\",\n \"External stylesheets are not loaded by email clients; use inline styles or `stylesheet`.\",\n );\n if (name === \"style\") {\n inStyle = true;\n styleText = \"\";\n add(\n \"safety.style-tag\",\n \"warning\",\n \"Move `<style>` rules into the definition's `stylesheet`, which the renderer places in `<mj-head>`.\",\n );\n }\n if (name === \"div\" && DIV_LAYOUT.test(style))\n add(\n \"safety.div-layout\",\n \"warning\",\n \"A `<div>` carrying width or float is unreliable in Outlook; lay out with tables.\",\n );\n if (name === \"img\") {\n if (!(\"alt\" in attrs))\n add(\n \"safety.img-alt\",\n \"warning\",\n 'Every `<img>` needs an `alt` attribute (`alt=\"\"` for decorative images), ideally bound to a field.',\n );\n if (!(\"width\" in attrs))\n add(\n \"safety.img-width\",\n \"warning\",\n \"Give `<img>` a `width` attribute; Outlook ignores CSS widths on images.\",\n );\n if ((attrs.src ?? \"\") === \"\")\n add(\n \"safety.empty-img-src\",\n \"warning\",\n `An \\`<img>\\` renders with an empty \\`src\\` in the \"${state}\" state; wrap it in \\`{% if <field> != blank %}\\` (Liquid treats an empty string as true, so a bare \\`{% if <field> %}\\` doesn't hide it).`,\n );\n }\n },\n ontext(text) {\n if (inStyle) styleText += text;\n },\n onclosetag(name) {\n if (name === \"style\") {\n inStyle = false;\n if (/@import\\b/i.test(stripCssComments(styleText)))\n add(\"safety.import\", \"error\", IMPORT_MESSAGE);\n issues.push(...cssLayoutIssues(styleText));\n }\n },\n },\n { decodeEntities: true },\n );\n parser.write(html);\n parser.end();\n }\n if (def.stylesheet) issues.push(...stylesheetIssues(def, def.stylesheet));\n\n const seen = new Set<string>();\n return issues.filter((i) => {\n const k = `${i.ruleId}\\0${i.path ?? \"\"}\\0${i.message}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n}\n","// Accessibility over a custom block's rendered HTML, on every specimen state.\n// Built-in blocks get @templatical/quality's rules from structured fields; a\n// custom block is opaque HTML, so these read the markup itself. All are\n// warnings: they read inline markup only, and a block's colours or table use\n// can come from context they can't see.\n\nimport { Parser } from \"htmlparser2\";\nimport {\n getContrastRatio,\n isOpaqueHex,\n lintAccessibility,\n} from \"@templatical/quality\";\nimport {\n createDefaultTemplateContent,\n createParagraphBlock,\n} from \"@templatical/types\";\nimport type { RenderedState } from \"./render\";\nimport { declarations, stripCssUrlsAndStrings } from \"./safety\";\nimport type { CustomBlockIssue } from \"./types\";\n\nconst HEX = /#[0-9a-f]{3,8}\\b/i;\nconst PX = /^\\s*(\\d+(?:\\.\\d+)?)px/i;\n\ninterface Frame {\n color?: string;\n background?: string;\n fontSize?: number;\n}\n\n/** The colours and font size an element sets inline, over its parent's. */\nfunction frameFor(attrs: Record<string, string>, parent: Frame): Frame {\n const frame: Frame = { ...parent };\n if (attrs.bgcolor && isOpaqueHex(attrs.bgcolor))\n frame.background = attrs.bgcolor;\n for (const [prop, raw] of declarations(attrs.style ?? \"\")) {\n const value = raw.replace(/!important/i, \"\").trim();\n if (prop === \"color\" && isOpaqueHex(value)) frame.color = value;\n if (prop === \"background-color\" && isOpaqueHex(value))\n frame.background = value;\n if (prop === \"background\") {\n const hex = stripCssUrlsAndStrings(value).match(HEX)?.[0];\n if (hex && isOpaqueHex(hex)) frame.background = hex;\n }\n if (prop === \"font-size\") {\n const px = value.match(PX)?.[1];\n if (px) frame.fontSize = Number(px);\n }\n }\n return frame;\n}\n\n/** quality's own vague-link rule, run over the HTML as if it were a paragraph. */\nfunction vagueLinkMessages(html: string): string[] {\n const content = createDefaultTemplateContent();\n content.blocks = [createParagraphBlock({ content: html })];\n return lintAccessibility(content)\n .filter((i) => i.ruleId === \"a11y.link-vague-text\")\n .map((i) => i.message);\n}\n\nexport function checkAccessibility(\n rendered: RenderedState[],\n): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n const add = (ruleId: string, message: string) =>\n issues.push({ ruleId, severity: \"warning\", message });\n\n for (const { state, html } of rendered) {\n for (const message of vagueLinkMessages(html)) {\n add(\"a11y.link-vague-text\", message);\n }\n\n const frames: Frame[] = [{}];\n const tables: { presentation: boolean; hasHeader: boolean }[] = [];\n let link: { named: boolean } | null = null;\n\n const parser = new Parser(\n {\n onopentag(name, attrs) {\n frames.push(frameFor(attrs, frames[frames.length - 1]));\n if (name === \"table\") {\n const role = (attrs.role ?? \"\").toLowerCase();\n tables.push({\n presentation: role === \"presentation\" || role === \"none\",\n hasHeader: false,\n });\n }\n if (name === \"th\" && tables.length > 0) {\n tables[tables.length - 1].hasHeader = true;\n }\n if (name === \"a\") {\n link = {\n named:\n (attrs[\"aria-label\"] ?? \"\").trim() !== \"\" ||\n (attrs.title ?? \"\").trim() !== \"\",\n };\n }\n if (name === \"img\" && link && (attrs.alt ?? \"\").trim() !== \"\") {\n link.named = true;\n }\n },\n ontext(text) {\n const trimmed = text.trim();\n if (trimmed === \"\") return;\n if (link) link.named = true;\n const { color, background, fontSize } = frames[frames.length - 1];\n if (!color || !background) return;\n const required = (fontSize ?? 0) >= 24 ? 3 : 4.5;\n const ratio = getContrastRatio(color, background);\n if (Number.isNaN(ratio) || ratio >= required) return;\n add(\n \"a11y.contrast\",\n `Text in ${color} on ${background} has ${ratio.toFixed(2)}:1 contrast; it needs ${required}:1.`,\n );\n },\n onclosetag(name) {\n frames.pop();\n if (name === \"table\") {\n const table = tables.pop();\n if (table && !table.presentation && !table.hasHeader) {\n add(\n \"a11y.table-role\",\n 'A layout `<table>` needs `role=\"presentation\"`, or screen readers read it as a data table, cell by cell. A data table needs `<th>` headers instead.',\n );\n }\n }\n if (name === \"a\" && link) {\n if (!link.named) {\n add(\n \"a11y.link-no-name\",\n `A link has no text, \\`aria-label\\` or image \\`alt\\` in the \"${state}\" state, so a screen reader announces only \"link\".`,\n );\n }\n link = null;\n }\n },\n },\n { decodeEntities: true },\n );\n parser.write(html);\n parser.end();\n }\n\n const seen = new Set<string>();\n return issues.filter((i) => {\n const key = `${i.ruleId}\\0${i.message}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","","// Structural validation of a custom block definition: the generated\n// CustomBlockDefinition schema (dataSource excluded, see generate-schema.mjs),\n// the hand-written dataSourcePreview recipe schema, and the field-model checks\n// a JSON Schema can't express (duplicate keys, bounds that are ordered,\n// defaults within bounds). It takes a JSON working file or the object a\n// consumer's own module exports, `dataSource` function included, so the same\n// check can run in their CI over the definitions they ship.\n\nimport Ajv, { type ErrorObject } from \"ajv\";\nimport rawSchema from \"../../custom-block-schema.json\";\nimport type { CustomBlockCheckResult, CustomBlockIssue } from \"./types\";\n\nconst ajv = new Ajv({ allErrors: true, strict: false });\nconst validateDefinition = ajv.compile(rawSchema as object);\n\n// Owned by this package: the recipe is a template-tools concept, not part of\n// the SDK's block model, so it has no generated schema to drift from.\n// Reports the first problem only: a recipe is small, and one pointed message\n// reads better than a cascade of missing-property errors.\nconst validateRecipe = new Ajv({ allErrors: false, strict: false }).compile({\n type: \"object\",\n additionalProperties: false,\n required: [\"label\", \"request\", \"map\"],\n properties: {\n label: { type: \"string\", minLength: 1 },\n request: {\n type: \"object\",\n additionalProperties: false,\n required: [\"url\"],\n properties: {\n method: { enum: [\"GET\", \"POST\"] },\n url: { type: \"string\", minLength: 1 },\n headers: { type: \"object\", additionalProperties: { type: \"string\" } },\n body: { type: \"string\" },\n },\n },\n map: {\n type: \"object\",\n additionalProperties: { type: \"string\", minLength: 1 },\n },\n },\n});\n\nfunction schemaIssues(\n errors: ErrorObject[] | null | undefined,\n base = \"\",\n): CustomBlockIssue[] {\n return (errors ?? []).map((e) => ({\n ruleId: \"schema\",\n severity: \"error\",\n path: `${base}${e.instancePath}` || \"/\",\n message: `${base}${e.instancePath || \"\"} ${e.message ?? \"is invalid\"}${\n e.keyword === \"additionalProperties\"\n ? ` (\\`${(e.params as { additionalProperty: string }).additionalProperty}\\`)`\n : \"\"\n }`.trim(),\n }));\n}\n\ninterface FieldLike {\n key: string;\n type: string;\n default?: unknown;\n min?: number;\n max?: number;\n minItems?: number;\n maxItems?: number;\n options?: { value: string }[];\n fields?: FieldLike[];\n}\n\nconst isCount = (n: number | undefined) =>\n n === undefined || (Number.isInteger(n) && n >= 0);\n\nfunction fieldIssues(fields: FieldLike[], base: string): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n const seen = new Set<string>();\n fields.forEach((field, i) => {\n const path = `${base}/${i}`;\n if (seen.has(field.key)) {\n issues.push({\n ruleId: \"field.duplicate-key\",\n severity: \"error\",\n path,\n message: `Field key \\`${field.key}\\` is used twice; the second would overwrite the first's value.`,\n });\n }\n seen.add(field.key);\n if (\n field.type === \"number\" &&\n field.min !== undefined &&\n field.max !== undefined &&\n field.min > field.max\n ) {\n issues.push({\n ruleId: \"field.range\",\n severity: \"error\",\n path,\n message: `\\`${field.key}\\` has min ${field.min} above max ${field.max}, so no value is allowed.`,\n });\n }\n if (field.type === \"number\" && typeof field.default === \"number\") {\n const below = field.min !== undefined && field.default < field.min;\n const above = field.max !== undefined && field.default > field.max;\n if (below || above) {\n issues.push({\n ruleId: \"field.default-range\",\n severity: \"error\",\n path: `${path}/default`,\n message: `\\`${field.key}\\` defaults to ${field.default}, outside ${field.min ?? \"-∞\"}–${field.max ?? \"∞\"}.`,\n });\n }\n }\n if (\n field.type === \"select\" &&\n field.default !== undefined &&\n !(field.options ?? []).some((o) => o.value === field.default)\n ) {\n issues.push({\n ruleId: \"field.default-option\",\n severity: \"error\",\n path: `${path}/default`,\n message: `\\`${field.key}\\` defaults to \"${String(field.default)}\", which is not one of its options.`,\n });\n }\n if (field.type === \"repeatable\") {\n const { minItems, maxItems } = field;\n if (!isCount(minItems) || !isCount(maxItems)) {\n issues.push({\n ruleId: \"field.items-range\",\n severity: \"error\",\n path,\n message: `\\`${field.key}\\`'s minItems and maxItems must be whole numbers of 0 or more.`,\n });\n } else if (\n minItems !== undefined &&\n maxItems !== undefined &&\n minItems > maxItems\n ) {\n issues.push({\n ruleId: \"field.items-range\",\n severity: \"error\",\n path,\n message: `\\`${field.key}\\` has minItems ${minItems} above maxItems ${maxItems}, so no list length is allowed.`,\n });\n } else if (Array.isArray(field.default)) {\n const n = field.default.length;\n if (\n (minItems !== undefined && n < minItems) ||\n (maxItems !== undefined && n > maxItems)\n ) {\n issues.push({\n ruleId: \"field.default-items\",\n severity: \"error\",\n path: `${path}/default`,\n message: `\\`${field.key}\\` defaults to ${n} item(s), outside ${minItems ?? 0}–${maxItems ?? \"∞\"}; the palette would insert a list the editor won't allow.`,\n });\n }\n }\n if (field.fields) {\n issues.push(...fieldIssues(field.fields, `${path}/fields`));\n }\n }\n });\n return issues;\n}\n\n/**\n * A module's `dataSource` is `{ label, onFetch }` with a function, which the\n * schema can't describe (the generator hides functions). A JSON working file\n * can't hold a function at all, so there it is always an error that points at\n * `dataSourcePreview`.\n */\nfunction dataSourceIssues(dataSource: unknown): CustomBlockIssue[] {\n const ds = dataSource as { label?: unknown; onFetch?: unknown } | null;\n const issue = (message: string): CustomBlockIssue[] => [\n { ruleId: \"schema\", severity: \"error\", path: \"/dataSource\", message },\n ];\n if (ds === null || typeof ds !== \"object\" || Array.isArray(ds)) {\n return issue(\"`dataSource` must be an object with `label` and `onFetch`.\");\n }\n if (typeof ds.onFetch !== \"function\") {\n return issue(\n \"`dataSource.onFetch` must be a function. A JSON working file can't hold one: preview with `dataSourcePreview`, and write `dataSource` in the module at handoff.\",\n );\n }\n if (typeof ds.label !== \"string\" || ds.label === \"\") {\n return issue(\"`dataSource.label` must be a non-empty string.\");\n }\n return [];\n}\n\nexport function validateCustomBlockDefinition(\n data: unknown,\n): CustomBlockCheckResult {\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return {\n valid: false,\n issues: [\n {\n ruleId: \"schema\",\n severity: \"error\",\n path: \"/\",\n message: \"A custom block definition must be a JSON object.\",\n },\n ],\n };\n }\n const { dataSourcePreview, dataSource, ...definition } = data as Record<\n string,\n unknown\n >;\n const issues: CustomBlockIssue[] = [];\n if (dataSource !== undefined) issues.push(...dataSourceIssues(dataSource));\n if (!validateDefinition(definition)) {\n issues.push(...schemaIssues(validateDefinition.errors));\n }\n if (dataSourcePreview !== undefined && !validateRecipe(dataSourcePreview)) {\n issues.push(...schemaIssues(validateRecipe.errors, \"/dataSourcePreview\"));\n }\n if (issues.length === 0) {\n issues.push(\n ...fieldIssues((definition as { fields: FieldLike[] }).fields, \"/fields\"),\n );\n }\n return { valid: !issues.some((i) => i.severity === \"error\"), issues };\n}\n","// Static analysis of a custom block's Liquid template against its field\n// model. The engine options must equal the editor's renderer\n// (packages/editor/src/composables/useBlockRegistry.ts): a template that\n// renders here and fails there, or the reverse, makes every check below lie.\n// tests/custom-block-liquid.test.ts holds the two equal.\n\nimport { ForTag, Liquid, Output, Tag, type Template } from \"liquidjs\";\nimport type { CustomBlockIssue, CustomBlockWorkingFile } from \"./types\";\n\nexport function createLiquid(): Liquid {\n // `templates: {}` resolves include/render/layout against an empty in-memory\n // map, never the disk. Without it, Node's engine reads files relative to\n // the cwd: a recipe body could carry `.env` to the API host, and a template\n // would render here while the editor, which has no partials, fails.\n return new Liquid({\n strictVariables: false,\n strictFilters: false,\n templates: {},\n });\n}\n\n// `forloop` is Liquid's own loop object, never a field.\nconst LIQUID_GLOBALS = new Set([\"forloop\"]);\n\n// Tags that load another template file. A custom block's template and a\n// recipe are each one string, so nothing exists for them to load.\nconst FILE_TAGS = new Set([\"include\", \"render\", \"layout\"]);\n\n// Static analysis follows include/render into the partial by default, which\n// the engine refuses to load, so every analysis call skips partials. The\n// file-tag rule reports those tags instead.\nexport const NO_PARTIALS = { partials: false } as const;\n\n/** A tag's child templates, from liquidjs's static-analysis generator. */\nfunction childTemplates(template: Template): Template[] {\n if (!template.children) return [];\n const gen = template.children(false, true);\n let step = gen.next();\n while (!step.done) step = gen.next();\n return step.value ?? [];\n}\n\n/** Every template in the tree, depth-first, nested tags included. */\nfunction allTemplates(templates: Template[]): Template[] {\n return templates.flatMap((t) => [t, ...allTemplates(childTemplates(t))]);\n}\n\n/** Every `for` tag in the tree, nested ones included. */\nfunction forTags(templates: Template[]): ForTag[] {\n return allTemplates(templates).filter(\n (t): t is ForTag => t instanceof ForTag,\n );\n}\n\n/** Names of the file-loading tags in the tree, each once, in source order. */\nexport function fileTags(templates: Template[]): string[] {\n const names = allTemplates(templates)\n .filter((t): t is Tag => t instanceof Tag && FILE_TAGS.has(t.name))\n .map((t) => t.name);\n return [...new Set(names)];\n}\n\n/** How many `{{ }}` outputs in the tree reach the text without a `json` filter. */\nexport function outputsWithoutJson(templates: Template[]): number {\n return allTemplates(templates).filter(\n (t) =>\n t instanceof Output && !t.value.filters.some((f) => f.name === \"json\"),\n ).length;\n}\n\n/** The message for a template or recipe that uses a file-loading tag. */\nexport function fileTagMessage(name: string, where: string): string {\n return `\\`{% ${name} %}\\` loads another template file, and ${where} has none to load. Write its content inline instead.`;\n}\n\n/**\n * The field a loop iterates, when its collection is a bare identifier.\n * `for x in a.b` iterates a property of `a`, not `a`, so it has none.\n */\nfunction bareCollection(tag: ForTag): string | null {\n const c = tag.collection as {\n variable?: unknown;\n props?: Array<{ content?: unknown }>;\n };\n if (c.variable !== undefined || c.props?.length !== 1) return null;\n const name = c.props[0].content;\n return typeof name === \"string\" ? name : null;\n}\n\nexport function checkLiquid(def: CustomBlockWorkingFile): CustomBlockIssue[] {\n const engine = createLiquid();\n let parsed: Template[];\n try {\n parsed = engine.parse(def.template);\n } catch (err) {\n return [\n {\n ruleId: \"liquid.parse\",\n severity: \"error\",\n path: \"/template\",\n message: `The template does not parse: ${(err as Error).message}`,\n },\n ];\n }\n\n const issues: CustomBlockIssue[] = fileTags(parsed).map((name) => ({\n ruleId: \"liquid.file-tag\",\n severity: \"error\",\n path: \"/template\",\n message: fileTagMessage(name, \"a custom block\"),\n }));\n const fields = new Map(\n def.fields.map((f, i) => [f.key, { field: f, index: i }]),\n );\n\n const globals = new Set(\n engine\n .globalVariableSegmentsSync(def.template, NO_PARTIALS)\n .map((segments) => String(segments[0]))\n .filter((name) => !LIQUID_GLOBALS.has(name)),\n );\n for (const name of globals) {\n if (!fields.has(name)) {\n issues.push({\n ruleId: \"liquid.undefined-variable\",\n severity: \"error\",\n path: \"/template\",\n message: `The template reads \\`${name}\\`, which no field defines.`,\n });\n }\n }\n // A field only the data-source recipe reads (the product id it fetches by)\n // is in use; recipe Liquid that fails to parse is reported by checkRecipe.\n const recipeReads = new Set<string>();\n const request = def.dataSourcePreview?.request;\n for (const text of [request?.url, request?.body]) {\n if (text === undefined) continue;\n try {\n for (const [name] of engine.globalVariableSegmentsSync(\n text,\n NO_PARTIALS,\n )) {\n recipeReads.add(String(name));\n }\n } catch {\n /* reported by checkRecipe */\n }\n }\n for (const [key, { index }] of fields) {\n if (!globals.has(key) && !recipeReads.has(key)) {\n issues.push({\n ruleId: \"liquid.unused-field\",\n severity: \"warning\",\n path: `/fields/${index}`,\n message: `Field \\`${key}\\` is never read by the template or the data-source recipe.`,\n });\n }\n }\n\n // Each loop's body is checked against that loop's own collection, so two\n // loops may reuse one local name over different repeatables.\n for (const loop of forTags(parsed)) {\n const local = loop.variable;\n const collection = bareCollection(loop);\n if (collection === null) continue;\n const target = fields.get(collection)?.field;\n if (!target) continue;\n if (target.type !== \"repeatable\") {\n issues.push({\n ruleId: \"liquid.loop-not-repeatable\",\n severity: \"error\",\n path: \"/template\",\n message: `\\`{% for ${local} in ${collection} %}\\` iterates \\`${collection}\\`, which is a ${target.type} field, not a repeatable.`,\n });\n continue;\n }\n for (const [head, sub] of engine.variableSegmentsSync(\n loop.templates,\n NO_PARTIALS,\n )) {\n if (String(head) !== local || typeof sub !== \"string\") continue;\n if (!target.fields.some((f) => f.key === sub)) {\n issues.push({\n ruleId: \"liquid.unknown-item-key\",\n severity: \"error\",\n path: \"/template\",\n message: `\\`${local}.${sub}\\` reads \\`${sub}\\`, which \\`${collection}\\`'s items don't define.`,\n });\n }\n }\n }\n return issues;\n}\n","// The preview-only data-source recipe. The live bridge runs it from Node so\n// the page never sees a secret and no CORS applies; the agent generates the\n// shipped onFetch from the same recipe, so preview and production agree.\n//\n// These rules keep secrets contained, and each is load-bearing:\n// - `${env:NAME}` resolves only in header values;\n// - field values interpolate only into `url` and `body`;\n// - the Liquid engine loads no files, so `{% include %}` can't put a local\n// file (`.env`) into the request;\n// - the URL's scheme and host are literal, every field value is URL-encoded\n// (non-scalars as JSON), and the rendered URL must keep the recipe's\n// origin, so no field value can send the headers to another host.\n// A field value can never pull an env var into the request, and a secret can\n// never be moved into the URL or off to a host the author didn't write.\n\nimport {\n createLiquid,\n fileTagMessage,\n fileTags,\n NO_PARTIALS,\n outputsWithoutJson,\n} from \"./liquid\";\nimport type {\n CustomBlockIssue,\n CustomBlockWorkingFile,\n DataSourcePreview,\n} from \"./types\";\n\nconst ENV_REF = /\\$\\{env:([A-Za-z_][A-Za-z0-9_]*)\\}/g;\nconst LIQUID = /\\{\\{|\\{%/;\nconst UNSAFE_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n// A header-name word that names a credential. Whole words of the name split\n// on - and _, so X-Session-Locale and Accept-Keys don't match while X-API-Key,\n// X-Auth-Token and X-AccessToken do. `session` counts only before `id`.\nconst CREDENTIAL_WORD =\n /^(?:authorization|auth|cookie|key|apikey|password|credentials?|sessionid)$|(?:token|secret)$/;\n\nfunction namesCredential(header: string): boolean {\n const words = header.toLowerCase().split(/[-_]/);\n return words.some(\n (w, i) =>\n CREDENTIAL_WORD.test(w) || (w === \"session\" && words[i + 1] === \"id\"),\n );\n}\n\nconst HOST_TEMPLATE_ERROR =\n \"The request URL's scheme and host must be literal; Liquid may only follow the first `/` after the host.\";\n\n/**\n * Where the URL's scheme and authority end: the first `/`, `?` or `#` after\n * `://`, or the whole string when there is no `://`. Anything before this\n * index decides the origin, so it must hold no Liquid.\n */\nfunction authorityEnd(url: string): number {\n const scheme = url.indexOf(\"://\");\n if (scheme < 0) return url.length;\n const rest = url.slice(scheme + 3).search(/[/?#]/);\n return rest < 0 ? url.length : scheme + 3 + rest;\n}\n\n/** The recipe URL's origin, or null when Liquid can reach it. */\nfunction literalOrigin(url: string): string | null {\n const prefix = url.slice(0, authorityEnd(url));\n if (LIQUID.test(prefix)) return null;\n try {\n return new URL(prefix).origin;\n } catch {\n return null;\n }\n}\n\nfunction encodeValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"string\" || typeof value === \"number\") {\n return encodeURIComponent(String(value));\n }\n return encodeURIComponent(JSON.stringify(value));\n}\n\nexport function getPath(value: unknown, path: string): unknown {\n const parts = path\n .replace(/\\[(\\d+)\\]/g, \".$1\")\n .split(\".\")\n .filter(Boolean);\n let cur: unknown = value;\n for (const part of parts) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n}\n\nexport function checkRecipe(def: CustomBlockWorkingFile): CustomBlockIssue[] {\n const r = def.dataSourcePreview;\n if (!r) return [];\n const issues: CustomBlockIssue[] = [];\n const keys = new Set(def.fields.map((f) => f.key));\n const add =\n (severity: CustomBlockIssue[\"severity\"]) =>\n (ruleId: string, path: string, message: string) =>\n issues.push({\n ruleId,\n severity,\n path: `/dataSourcePreview${path}`,\n message,\n });\n const err = add(\"error\");\n const warn = add(\"warning\");\n const engine = createLiquid();\n\n for (const [part, text] of [\n [\"/request/url\", r.request.url],\n [\"/request/body\", r.request.body],\n ] as const) {\n if (text === undefined) continue;\n if (new RegExp(ENV_REF.source).test(text)) {\n err(\n \"recipe.env-outside-headers\",\n part,\n \"`${env:…}` is resolved only in header values; keep secrets out of the URL and body.\",\n );\n }\n try {\n for (const [name] of engine.globalVariableSegmentsSync(\n text,\n NO_PARTIALS,\n )) {\n if (!keys.has(String(name)))\n err(\n \"recipe.undefined-variable\",\n part,\n `The recipe reads \\`${String(name)}\\`, which no field defines.`,\n );\n }\n // The engine can't read files, so such a recipe would fail at fetch\n // time; refusing it here keeps the bridge from running it at all.\n const parsed = engine.parse(text);\n for (const name of fileTags(parsed)) {\n err(\"recipe.file-tag\", part, fileTagMessage(name, \"a recipe\"));\n }\n // Body values go in raw, so a quote typed into a field ends the string\n // early. `| json` quotes and escapes the value.\n if (\n part === \"/request/body\" &&\n /^\\s*[[{]/.test(text) &&\n outputsWithoutJson(parsed) > 0\n ) {\n warn(\n \"recipe.body-unescaped\",\n part,\n \"A JSON body interpolates a value without `| json`, so a quote in it breaks the JSON. Write `{{ key | json }}`, which adds the quotes.\",\n );\n }\n } catch (e) {\n err(\n \"recipe.undefined-variable\",\n part,\n `Does not parse: ${(e as Error).message}`,\n );\n }\n }\n // Without `://` the URL is not absolute, which recipe.url below reports.\n if (\n r.request.url.includes(\"://\") &&\n LIQUID.test(r.request.url.slice(0, authorityEnd(r.request.url)))\n ) {\n err(\"recipe.url-host-template\", \"/request/url\", HOST_TEMPLATE_ERROR);\n }\n const probeUrl = replaceLiquid(r.request.url);\n let parses = true;\n try {\n new URL(probeUrl);\n } catch {\n parses = false;\n }\n if (!/^https?:\\/\\//i.test(probeUrl) || !parses) {\n err(\n \"recipe.url\",\n \"/request/url\",\n \"The request URL must be an absolute http(s) URL.\",\n );\n }\n if (r.request.body !== undefined && (r.request.method ?? \"GET\") !== \"POST\") {\n err(\n \"recipe.body-without-post\",\n \"/request/body\",\n 'A `body` is sent only with `\"method\": \"POST\"`; a GET request can\\'t carry one.',\n );\n }\n for (const [name, value] of Object.entries(r.request.headers ?? {})) {\n if (LIQUID.test(value)) {\n err(\n \"recipe.field-in-header\",\n `/request/headers/${name}`,\n `Header \\`${name}\\` interpolates a field; headers take only \\`\\${env:NAME}\\`.`,\n );\n } else if (\n namesCredential(name) &&\n !new RegExp(ENV_REF.source).test(value)\n ) {\n err(\n \"recipe.literal-secret\",\n `/request/headers/${name}`,\n `Header \\`${name}\\` holds a literal credential. Use \\`\\${env:NAME}\\` and export the variable instead.`,\n );\n }\n }\n for (const field of Object.keys(r.map)) {\n if (!keys.has(field))\n err(\n \"recipe.unknown-map-key\",\n `/map/${field}`,\n `\\`map\\` writes \\`${field}\\`, which no field defines.`,\n );\n }\n return issues;\n}\n\nexport interface RecipeOptions {\n env?: Record<string, string | undefined>;\n fetch?: typeof fetch;\n timeoutMs?: number;\n}\nexport type RecipeResult =\n | {\n ok: true;\n status: number;\n values: Record<string, unknown>;\n unmapped: string[];\n }\n | { ok: false; status?: number; error: string };\n\nexport async function runRecipe(\n recipe: DataSourcePreview,\n fieldValues: Record<string, unknown>,\n {\n env = process.env,\n fetch: doFetch = fetch,\n timeoutMs = 10_000,\n }: RecipeOptions = {},\n): Promise<RecipeResult> {\n const expectedOrigin = literalOrigin(recipe.request.url);\n if (expectedOrigin === null) return { ok: false, error: HOST_TEMPLATE_ERROR };\n const engine = createLiquid();\n const encoded = Object.fromEntries(\n Object.entries(fieldValues).map(([k, v]) => [k, encodeValue(v)]),\n );\n let url: string;\n let body: string | undefined;\n try {\n url = String(await engine.parseAndRender(recipe.request.url, encoded));\n body =\n recipe.request.body === undefined\n ? undefined\n : String(await engine.parseAndRender(recipe.request.body, fieldValues));\n } catch (e) {\n return {\n ok: false,\n error: `The request template does not render: ${(e as Error).message}`,\n };\n }\n\n let renderedOrigin: string | null = null;\n try {\n renderedOrigin = new URL(url).origin;\n } catch {\n /* reported below */\n }\n // Unreachable while the URL authority refuses Liquid and every value is\n // encoded; it stops the proxy being repointed if either guard regresses.\n /* v8 ignore start */\n if (renderedOrigin !== expectedOrigin) {\n return {\n ok: false,\n error: `The rendered URL's origin (${renderedOrigin ?? \"unparseable\"}) differs from the recipe's (${expectedOrigin}); no request was made.`,\n };\n }\n /* v8 ignore stop */\n\n const secrets: string[] = [];\n const headers: Record<string, string> = {};\n for (const [name, template] of Object.entries(recipe.request.headers ?? {})) {\n let missing: string | undefined;\n headers[name] = template.replace(ENV_REF, (_, variable: string) => {\n const value = env[variable];\n if (value === undefined || value === \"\") {\n missing ??= variable;\n return \"\";\n }\n secrets.push(value);\n return value;\n });\n if (missing) {\n return {\n ok: false,\n error: `Environment variable ${missing} is not set. Export it in the shell that runs the live server.`,\n };\n }\n }\n const redact = (s: string) =>\n secrets.reduce((acc, secret) => acc.split(secret).join(\"***\"), s);\n const redactDeep = (v: unknown): unknown => {\n if (typeof v === \"string\") return redact(v);\n if (Array.isArray(v)) return v.map(redactDeep);\n if (v !== null && typeof v === \"object\") {\n return Object.fromEntries(\n Object.entries(v).map(([k, x]) => [k, redactDeep(x)]),\n );\n }\n return v;\n };\n const method = recipe.request.method ?? \"GET\";\n\n try {\n const res = await doFetch(url, {\n method,\n headers,\n body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (res.status >= 300 && res.status < 400) {\n const location = res.headers.get(\"location\") ?? \"(none)\";\n return {\n ok: false,\n status: res.status,\n error: redact(\n `${method} ${url} redirected (${res.status}) to ${location}. Redirects are not followed; point the recipe at the final URL.`,\n ),\n };\n }\n const text = await res.text();\n if (!res.ok) {\n return {\n ok: false,\n status: res.status,\n error: `${redact(`${method} ${url} returned ${res.status}: `)}${redact(text).slice(0, 200)}`,\n };\n }\n let json: unknown;\n try {\n json = JSON.parse(text);\n } catch {\n return {\n ok: false,\n status: res.status,\n error: redact(`${method} ${url} did not return JSON.`),\n };\n }\n const values: Record<string, unknown> = {};\n const unmapped: string[] = [];\n for (const [field, path] of Object.entries(recipe.map)) {\n if (UNSAFE_KEYS.has(field)) continue;\n const v = getPath(json, path);\n if (v === undefined) unmapped.push(field);\n else values[field] = redactDeep(v);\n }\n return { ok: true, status: res.status, values, unmapped };\n } catch (e) {\n return {\n ok: false,\n error: redact(`${method} ${url} failed: ${(e as Error).message}`),\n };\n }\n}\n\n/** Replaces each `{{…}}` / `{%…%}` span with \"x\" in one linear pass; an unterminated opener swallows the rest. */\nexport function replaceLiquid(text: string): string {\n const MISSING = Infinity;\n const next = (needle: string, from: number) => {\n const at = text.indexOf(needle, from);\n return at === -1 ? MISSING : at;\n };\n let out = \"\";\n let i = 0;\n // Each opener is searched for again only once the cursor passes its cached\n // position, so an opener that never recurs is scanned for once, not per span.\n let a = next(\"{{\", 0);\n let b = next(\"{%\", 0);\n for (;;) {\n if (a < i) a = next(\"{{\", i);\n if (b < i) b = next(\"{%\", i);\n const open = Math.min(a, b);\n if (open === MISSING) return out + text.slice(i);\n out += text.slice(i, open);\n const close = text.indexOf(open === a ? \"}}\" : \"%}\", open + 2);\n if (close === -1) return out + \"x\";\n out += \"x\";\n i = close + 2;\n }\n}\n","// The specimen sheet: one instance of the block per state that commonly\n// breaks a custom block — defaults, every optional cleared, long copy,\n// repeatables at their bounds, booleans inverted — so the live preview shows\n// the edge cases without the user thinking to try them.\n\nimport { escapeHtml } from \"@templatical/renderer\";\nimport {\n createCustomBlock,\n createDefaultTemplateContent,\n createParagraphBlock,\n type Block,\n type CustomBlockDefinition,\n type CustomBlockField,\n type TemplateContent,\n} from \"@templatical/types\";\nimport type { CustomBlockWorkingFile } from \"./types\";\n\nexport type SpecimenState =\n \"defaults\" | \"empty\" | \"long\" | \"min-items\" | \"max-items\" | \"flipped\";\nexport const SPECIMEN_STATES: readonly SpecimenState[] = [\n \"defaults\",\n \"empty\",\n \"long\",\n \"min-items\",\n \"max-items\",\n \"flipped\",\n];\nexport interface SpecimenInstance {\n state: SpecimenState;\n fieldValues: Record<string, unknown>;\n}\nexport const DEFAULT_MAX_ITEMS = 6;\n\ntype ItemField = Exclude<CustomBlockField, { type: \"repeatable\" }>;\ntype Repeatable = Extract<CustomBlockField, { type: \"repeatable\" }>;\n\n// Same fallbacks as @templatical/types' createCustomBlock, which keeps its own\n// copy private; the \"defaults\" test holds the two equal.\nfunction fieldDefault(f: CustomBlockField): unknown {\n if (f.default !== undefined) return structuredClone(f.default);\n if (f.type === \"repeatable\") return [];\n if (f.type === \"boolean\") return false;\n if (f.type === \"number\") return 0;\n return \"\";\n}\n\nfunction itemFrom(fields: ItemField[]): Record<string, unknown> {\n return Object.fromEntries(fields.map((f) => [f.key, fieldDefault(f)]));\n}\n\nfunction items(f: Repeatable, n: number): Record<string, unknown>[] {\n const seeds =\n Array.isArray(f.default) && f.default.length > 0\n ? f.default\n : [itemFrom(f.fields)];\n return Array.from({ length: n }, (_, i) =>\n structuredClone(seeds[i % seeds.length]),\n );\n}\n\nfunction lengthen(f: CustomBlockField, v: unknown): unknown {\n if (f.type === \"text\" || f.type === \"textarea\") {\n const base = (typeof v === \"string\" && v) || f.placeholder || f.label;\n return [base, base, base].join(\" \");\n }\n if (f.type === \"number\") return f.max ?? v;\n if (f.type === \"repeatable\" && Array.isArray(v)) {\n return v.map((item: Record<string, unknown>) =>\n Object.fromEntries(\n f.fields.map((s) => [s.key, lengthen(s, item[s.key])]),\n ),\n );\n }\n return v;\n}\n\nfunction clear(f: CustomBlockField, v: unknown): unknown {\n if (f.required) return v;\n if (f.type === \"boolean\") return false;\n if (f.type === \"number\") return null;\n if (f.type === \"repeatable\") return items(f, f.minItems ?? 0);\n return \"\";\n}\n\nexport function buildSpecimen(def: CustomBlockWorkingFile): SpecimenInstance[] {\n const defaults = createCustomBlock(def as CustomBlockDefinition).fieldValues;\n const map = (fn: (f: CustomBlockField, v: unknown) => unknown) =>\n Object.fromEntries(\n def.fields.map((f) => [f.key, fn(f, structuredClone(defaults[f.key]))]),\n );\n\n const out: SpecimenInstance[] = [\n { state: \"defaults\", fieldValues: structuredClone(defaults) },\n { state: \"empty\", fieldValues: map(clear) },\n { state: \"long\", fieldValues: map(lengthen) },\n ];\n const repeatables = def.fields.filter(\n (f): f is Repeatable => f.type === \"repeatable\",\n );\n if (repeatables.length > 0) {\n const sized = (pick: (f: Repeatable) => number) => ({\n ...structuredClone(defaults),\n ...Object.fromEntries(repeatables.map((f) => [f.key, items(f, pick(f))])),\n });\n out.push({\n state: \"min-items\",\n fieldValues: sized((f) => f.minItems ?? 0),\n });\n // Never below minItems: without a maxItems, the fallback can sit under\n // the minimum, and the editor never lets a list get that short.\n out.push({\n state: \"max-items\",\n fieldValues: sized((f) =>\n Math.max(f.minItems ?? 0, f.maxItems ?? DEFAULT_MAX_ITEMS),\n ),\n });\n }\n const booleans = def.fields.filter((f) => f.type === \"boolean\");\n if (booleans.length > 0) {\n out.push({\n state: \"flipped\",\n fieldValues: {\n ...structuredClone(defaults),\n ...Object.fromEntries(booleans.map((f) => [f.key, !defaults[f.key]])),\n },\n });\n }\n return out;\n}\n\n/** What running the data-source recipe for the live preview produced. */\nexport type FetchedSpecimen =\n | { ok: true; fieldValues: Record<string, unknown> }\n | { ok: false; error: string };\n\n/**\n * The live preview's \"fetched\" state: the block filled from the real\n * endpoint and marked fetched, so read-only fields lock as they do in\n * production. A failed request puts its error on the label instead.\n */\nexport function fetchedSpecimenBlocks(\n def: CustomBlockWorkingFile,\n fetched: FetchedSpecimen,\n): Block[] {\n const id = \"specimen-fetched\";\n if (!fetched.ok) {\n return [\n createParagraphBlock({\n id: `${id}-label`,\n content: `<p><strong>Specimen:</strong> fetched. The request failed: ${escapeHtml(fetched.error)}</p>`,\n }),\n ];\n }\n return [\n createParagraphBlock({\n id: `${id}-label`,\n content: \"<p><strong>Specimen:</strong> fetched</p>\",\n }),\n {\n ...createCustomBlock(def as CustomBlockDefinition),\n id,\n fieldValues: fetched.fieldValues,\n dataSourceFetched: true,\n },\n ];\n}\n\n/** The id each state's custom block gets in the specimen sheet; its label is `${id}-label`. */\nexport function specimenBlockId(state: SpecimenState): string {\n return `specimen-${state}`;\n}\n\nexport function buildSpecimenTemplate(\n def: CustomBlockWorkingFile,\n host?: TemplateContent,\n only?: SpecimenState,\n): TemplateContent {\n const content = host ? structuredClone(host) : createDefaultTemplateContent();\n for (const instance of buildSpecimen(def)) {\n if (only && instance.state !== only) continue;\n // Fixed ids, not fresh ones: the sheet is rebuilt on every reload and\n // page load, and a note left on a block keys on its id. A random id\n // would orphan every note at the next reload.\n const id = specimenBlockId(instance.state);\n content.blocks.push(\n createParagraphBlock({\n id: `${id}-label`,\n content: `<p><strong>Specimen:</strong> ${instance.state}</p>`,\n }),\n {\n ...createCustomBlock(def as CustomBlockDefinition),\n id,\n fieldValues: instance.fieldValues,\n },\n );\n }\n return content;\n}\n","// Renders a custom block the way the editor and the renderer do: Liquid over\n// fieldValues for the HTML, then renderToMjml with that HTML and the\n// definition's stylesheet, which the renderer emits once into <mj-head>.\n\nimport { renderToMjml } from \"@templatical/renderer\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport { createLiquid } from \"./liquid\";\nimport {\n buildSpecimen,\n buildSpecimenTemplate,\n type SpecimenInstance,\n type SpecimenState,\n} from \"./specimen\";\nimport type { CustomBlockWorkingFile } from \"./types\";\n\nexport interface RenderedState {\n state: SpecimenState;\n html: string;\n}\n\nexport async function renderStates(\n def: CustomBlockWorkingFile,\n instances: SpecimenInstance[] = buildSpecimen(def),\n): Promise<RenderedState[]> {\n const liquid = createLiquid();\n return Promise.all(\n instances.map(async (i) => ({\n state: i.state,\n html: String(await liquid.parseAndRender(def.template, i.fieldValues)),\n })),\n );\n}\n\nexport async function renderSpecimenMjml(\n def: CustomBlockWorkingFile,\n template: TemplateContent = buildSpecimenTemplate(def),\n): Promise<string> {\n const liquid = createLiquid();\n return renderToMjml(template, {\n renderCustomBlock: async (block) =>\n String(await liquid.parseAndRender(def.template, block.fieldValues)),\n getCustomBlockStylesheet: (customType) =>\n customType === def.type ? def.stylesheet : undefined,\n });\n}\n","// The full `custom-block validate` pipeline, as a library function. Stages\n// short-circuit where a later stage would only report noise: no Liquid or\n// render checks on a structurally broken definition, no render on a template\n// that doesn't parse or loads another file, and no lint on one that fails to\n// render.\n\nimport { checkAccessibility } from \"./a11y\";\nimport { validateCustomBlockDefinition } from \"./definition\";\nimport { checkLiquid } from \"./liquid\";\nimport { checkRecipe } from \"./recipe\";\nimport { renderSpecimenMjml, renderStates, type RenderedState } from \"./render\";\nimport { checkEmailSafety } from \"./safety\";\nimport type {\n CustomBlockCheckResult,\n CustomBlockIssue,\n CustomBlockWorkingFile,\n} from \"./types\";\n\nexport interface CheckCustomBlockOptions {\n compileMjml?: (mjml: string) => Promise<{ errors: unknown[] }>;\n}\n\nconst finish = (issues: CustomBlockIssue[]): CustomBlockCheckResult => ({\n valid: !issues.some((i) => i.severity === \"error\"),\n issues,\n});\n\nexport async function checkCustomBlock(\n data: unknown,\n { compileMjml }: CheckCustomBlockOptions = {},\n): Promise<CustomBlockCheckResult> {\n const structural = validateCustomBlockDefinition(data);\n if (!structural.valid) return structural;\n const def = data as CustomBlockWorkingFile;\n\n const issues = [...structural.issues, ...checkRecipe(def)];\n const liquid = checkLiquid(def);\n issues.push(...liquid);\n if (\n liquid.some(\n (i) => i.ruleId === \"liquid.parse\" || i.ruleId === \"liquid.file-tag\",\n )\n )\n return finish(issues);\n\n // A template can parse and still throw while rendering; that is a finding\n // about the definition, not a crash of the check.\n let rendered: RenderedState[];\n try {\n rendered = await renderStates(def);\n } catch (err) {\n issues.push({\n ruleId: \"liquid.render\",\n severity: \"error\",\n path: \"/template\",\n message: `The template does not render: ${(err as Error).message}`,\n });\n return finish(issues);\n }\n issues.push(...checkEmailSafety(def, rendered));\n issues.push(...checkAccessibility(rendered));\n const mjml = await renderSpecimenMjml(def);\n // The renderer trims the stylesheet before emitting it.\n const stylesheet = def.stylesheet?.trim();\n if (stylesheet && !mjml.includes(stylesheet)) {\n issues.push({\n ruleId: \"render.stylesheet-missing\",\n severity: \"error\",\n message: \"The stylesheet did not reach <mj-head> in the rendered MJML.\",\n });\n }\n if (compileMjml) {\n for (const e of (await compileMjml(mjml)).errors) {\n const o = e as { formattedMessage?: string; message?: string };\n issues.push({\n ruleId: \"render.mjml\",\n severity: \"warning\",\n message: o.formattedMessage ?? o.message ?? String(e),\n });\n }\n }\n return finish(issues);\n}\n"],"mappings":";;;;;;;;;ACyBA,MAAa,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
1
+ {"version":3,"file":"custom-block-CsZN9qMM.js","names":["rawSchema","rawSchema"],"sources":["../schema.json","../src/validate.ts","../src/custom-block/safety.ts","../src/custom-block/a11y.ts","../custom-block-schema.json","../src/custom-block/definition.ts","../src/custom-block/liquid.ts","../src/custom-block/recipe.ts","../src/custom-block/specimen.ts","../src/custom-block/render.ts","../src/custom-block/check.ts"],"sourcesContent":["","// Structural validation of a Templatical template against the generated JSON\n// Schema, plus the @templatical/quality lint layered on top.\n//\n// Structural validation is discriminator-aware: each block is checked against\n// the subschema for its declared `type` (e.g. a block with `\"type\": \"button\"`\n// is validated as a ButtonBlock), so errors are precise (\"blocks[2] (button)\n// must have required property 'url'\") instead of the raw anyOf's \"must match\n// exactly one schema in anyOf\".\n\nimport Ajv, { type ValidateFunction, type ErrorObject } from \"ajv\";\nimport { lintTemplate, type LintIssue } from \"@templatical/quality\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport rawSchema from \"../schema.json\";\n\ninterface SchemaDefinition {\n properties?: { type?: { const?: unknown } } & Record<string, unknown>;\n [key: string]: unknown;\n}\n\ninterface SchemaDocument {\n definitions: Record<string, SchemaDefinition>;\n [key: string]: unknown;\n}\n\n/** The generated JSON Schema for `TemplateContent`, as a plain object. */\nexport const schema = rawSchema as unknown as SchemaDocument;\n\nconst ajv = new Ajv({ allErrors: true, strict: false });\n\n// Map a block's `type` discriminator (e.g. \"button\", \"social\") to its schema\n// definition name (e.g. \"ButtonBlock\", \"SocialIconsBlock\"), derived from the\n// schema itself so it never drifts from the generated defs.\nconst typeToDef: Record<string, string> = {};\nfor (const [name, def] of Object.entries(schema.definitions)) {\n const constType = def?.properties?.type?.const;\n if (typeof constType === \"string\") {\n typeToDef[constType] = name;\n }\n}\n\nconst validatorCache = new Map<string, ValidateFunction>();\n\nfunction validatorFor(defName: string): ValidateFunction {\n const cached = validatorCache.get(defName);\n if (cached) return cached;\n\n const definitions = structuredClone(schema.definitions);\n // A section's `children` holds nested blocks (Block[][]); we recurse into\n // them separately for precise paths, so stub the deep check here.\n if (defName === \"SectionBlock\") {\n const section = definitions.SectionBlock;\n if (section?.properties) {\n section.properties.children = { type: \"array\" };\n }\n }\n const compiled = ajv.compile({\n $ref: `#/definitions/${defName}`,\n definitions,\n });\n validatorCache.set(defName, compiled);\n return compiled;\n}\n\nconst settingsValidator = ajv.compile({\n $ref: \"#/definitions/TemplateSettings\",\n definitions: structuredClone(schema.definitions),\n});\n\ninterface FlatBlock {\n path: string;\n block: Record<string, unknown> | null;\n}\n\nfunction flattenBlocks(\n blocks: unknown,\n basePath: string,\n out: FlatBlock[],\n): void {\n if (!Array.isArray(blocks)) return;\n blocks.forEach((block, i) => {\n const path = `${basePath}[${i}]`;\n out.push({ path, block: block ?? null });\n if (block?.type === \"section\" && Array.isArray(block.children)) {\n block.children.forEach((column: unknown, ci: number) => {\n flattenBlocks(column, `${path}.children[${ci}]`, out);\n });\n }\n });\n}\n\nfunction formatAjvErrors(\n errors: ErrorObject[] | null | undefined,\n prefix: string,\n): string[] {\n return (errors ?? []).map((e) => {\n const where = `${prefix}${e.instancePath}`;\n const extra =\n e.keyword === \"additionalProperties\"\n ? ` (${(e.params as { additionalProperty?: string }).additionalProperty})`\n : \"\";\n return `${where} ${e.message}${extra}`;\n });\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Structural validation. Synchronous, depends only on ajv + the committed\n * schema.json (no build of the workspace packages required).\n */\nexport function validateTemplate(data: unknown): ValidationResult {\n const errors: string[] = [];\n\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return { valid: false, errors: [\"(root) must be an object\"] };\n }\n\n const doc = data as Record<string, unknown>;\n\n if (!Array.isArray(doc.blocks)) {\n errors.push(\"blocks must be an array\");\n }\n if (doc.settings === null || typeof doc.settings !== \"object\") {\n errors.push(\"settings must be an object\");\n } else if (!settingsValidator(doc.settings)) {\n errors.push(...formatAjvErrors(settingsValidator.errors, \"settings\"));\n }\n\n const flat: FlatBlock[] = [];\n flattenBlocks(doc.blocks, \"blocks\", flat);\n for (const { path, block } of flat) {\n const type = block?.type;\n const defName = typeof type === \"string\" ? typeToDef[type] : undefined;\n if (!defName) {\n const known = Object.keys(typeToDef).join(\", \");\n errors.push(\n `${path} has unknown or missing block type ${JSON.stringify(type)} (expected one of: ${known})`,\n );\n continue;\n }\n const validate = validatorFor(defName);\n if (!validate(block)) {\n errors.push(...formatAjvErrors(validate.errors, `${path} (${type})`));\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\nexport interface QualityLintResult {\n issues: LintIssue[];\n /** Set when the linter itself threw — the template is structurally suspect. */\n error?: string;\n}\n\n/**\n * Quality layer — accessibility / structure / link linting.\n *\n * Assumes structurally-valid input, so callers should run `validateTemplate`\n * first; the try/catch is a guard against a malformed template crashing the\n * linter rather than a substitute for that ordering.\n */\nexport function runQualityLint(data: unknown): QualityLintResult {\n try {\n return { issues: lintTemplate(data as TemplateContent) ?? [] };\n } catch (err) {\n return { issues: [], error: (err as Error).message };\n }\n}\n","// Email-client safety over a custom block's rendered HTML. Custom blocks are\n// raw HTML the SDK passes through untouched (inside <mj-text>), so nothing\n// downstream repairs a layout Outlook can't draw; this is the only check.\n// Rules run on every specimen state's output so both sides of each {% if %}\n// are seen, and an empty optional image surfaces as src=\"\".\n\nimport { Parser } from \"htmlparser2\";\nimport type { RenderedState } from \"./render\";\nimport type { CustomBlockIssue, CustomBlockWorkingFile } from \"./types\";\n\nconst FLEX_GRID = /(^|;)\\s*display\\s*:\\s*(inline-)?(flex|grid)\\b/i;\nconst POSITION = /(^|;)\\s*position\\s*:\\s*(absolute|fixed)\\b/i;\nconst DIV_LAYOUT = /(^|;)\\s*(width|max-width|float)\\s*:/i;\n// `var(--x)` anywhere: Gmail and Outlook drop custom properties, and with them\n// the whole declaration that reads one.\nconst CSS_VARIABLE = /\\bvar\\(\\s*--/i;\n// A colour value in a `background:` shorthand: hex, or an rgb()/hsl() call.\nconst COLOR_VALUE = /#[0-9a-f]{3,8}\\b|\\b(?:rgba?|hsla?)\\(/i;\nconst FORM_CONTROLS = new Set([\"form\", \"input\", \"select\", \"textarea\"]);\n\n// The same two layout rules over stylesheet text, where a declaration can\n// also follow `{` or a newline.\nconst CSS_FLEX_GRID = /(^|[;{\\s])display\\s*:\\s*(inline-)?(flex|grid)\\b/i;\nconst CSS_POSITION = /(^|[;{\\s])position\\s*:\\s*(absolute|fixed)\\b/i;\n\nconst IMPORT_MESSAGE =\n \"`@import` is dropped by most email clients; inline the rules.\";\nconst FLEX_GRID_MESSAGE =\n \"`display: flex`/`grid` is ignored by Outlook and Gmail on many clients; use tables.\";\nconst POSITION_MESSAGE =\n \"`position: absolute/fixed` is unsupported in email clients.\";\nconst CSS_VARIABLE_MESSAGE =\n \"CSS custom properties (`var(--…)`) are dropped by Gmail and Outlook, and the whole declaration with them; write the value itself.\";\n\n/** A style attribute's declarations as lowercase property → raw value pairs. */\nexport function declarations(style: string): [string, string][] {\n const out: [string, string][] = [];\n for (const part of style.split(\";\")) {\n const colon = part.indexOf(\":\");\n if (colon > 0) {\n out.push([\n part.slice(0, colon).trim().toLowerCase(),\n part.slice(colon + 1),\n ]);\n }\n }\n return out;\n}\n\n/**\n * Whether a background image here has a colour to fall back on when images\n * are blocked: `background-color`, a colour inside the `background:`\n * shorthand, or the `bgcolor` attribute Outlook reads.\n */\nfunction backgroundIssue(\n style: string,\n attrs: Record<string, string>,\n): boolean {\n const decls = declarations(style);\n const hasImage = decls.some(\n ([prop, value]) =>\n (prop === \"background\" || prop === \"background-image\") &&\n /url\\(/i.test(value),\n );\n if (!hasImage || \"bgcolor\" in attrs) return false;\n return !decls.some(\n ([prop, value]) =>\n prop === \"background-color\" ||\n (prop === \"background\" &&\n COLOR_VALUE.test(stripCssUrlsAndStrings(value))),\n );\n}\n\n/** Drops `/* … *\\/` comments in one linear pass; an unterminated comment drops the rest. */\nexport function stripCssComments(css: string): string {\n let out = \"\";\n let i = 0;\n for (;;) {\n const open = css.indexOf(\"/*\", i);\n if (open === -1) return out + css.slice(i);\n out += css.slice(i, open);\n const close = css.indexOf(\"*/\", open + 2);\n if (close === -1) return out;\n i = close + 2;\n }\n}\n\n/**\n * Remove `url(...)` and quoted strings so the class scan can't read `.png` or\n * `\"x.y\"` as a selector. Linear: each opener's next position is searched for\n * again only once the cursor has passed it, so an opener that never closes\n * costs one scan, not one per occurrence. An unterminated `url(` or quote\n * drops the rest, as a CSS parser would.\n */\nexport function stripCssUrlsAndStrings(css: string): string {\n const lower = css.toLowerCase();\n const next = (needle: string, from: number) => {\n const at = lower.indexOf(needle, from);\n return at === -1 ? Infinity : at;\n };\n const closers: Record<string, string> = { \"url(\": \")\", '\"': '\"', \"'\": \"'\" };\n const pos: Record<string, number> = { \"url(\": 0, '\"': 0, \"'\": 0 };\n for (const k of Object.keys(pos)) pos[k] = next(k, 0);\n let out = \"\";\n let i = 0;\n for (;;) {\n let opener = \"\";\n let open = Infinity;\n for (const k of Object.keys(pos)) {\n if (pos[k] < i) pos[k] = next(k, i);\n if (pos[k] < open) {\n open = pos[k];\n opener = k;\n }\n }\n if (open === Infinity) return out + css.slice(i);\n out += css.slice(i, open);\n const close = lower.indexOf(closers[opener], open + opener.length);\n if (close === -1) return out;\n i = close + 1;\n }\n}\n\n/** Layout rules that hold for stylesheet text: the definition's and a `<style>`'s. */\nfunction cssLayoutIssues(css: string, path?: string): CustomBlockIssue[] {\n const text = stripCssComments(css);\n const issues: CustomBlockIssue[] = [];\n const at = path === undefined ? {} : { path };\n if (CSS_FLEX_GRID.test(text))\n issues.push({\n ruleId: \"safety.flex-grid\",\n severity: \"error\",\n ...at,\n message: FLEX_GRID_MESSAGE,\n });\n if (CSS_POSITION.test(text))\n issues.push({\n ruleId: \"safety.position\",\n severity: \"error\",\n ...at,\n message: POSITION_MESSAGE,\n });\n if (CSS_VARIABLE.test(text))\n issues.push({\n ruleId: \"safety.css-variable\",\n severity: \"warning\",\n ...at,\n message: CSS_VARIABLE_MESSAGE,\n });\n return issues;\n}\n\nfunction stylesheetIssues(\n def: CustomBlockWorkingFile,\n css: string,\n): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n if (/@import\\b/i.test(stripCssComments(css))) {\n issues.push({\n ruleId: \"safety.import\",\n severity: \"error\",\n path: \"/stylesheet\",\n message: IMPORT_MESSAGE,\n });\n }\n issues.push(...cssLayoutIssues(css, \"/stylesheet\"));\n const stripped = stripCssUrlsAndStrings(stripCssComments(css));\n const prefix = `tplc-${def.type}-`;\n const unscoped = [\n ...new Set(\n [...stripped.matchAll(/\\.(-?[_a-zA-Z][\\w-]*)/g)].map((m) => m[1]),\n ),\n ].filter((cls) => !cls.startsWith(prefix));\n if (unscoped.length > 0) {\n issues.push({\n ruleId: \"safety.unscoped-class\",\n severity: \"warning\",\n path: \"/stylesheet\",\n message: `Classes ${unscoped.map((c) => `.${c}`).join(\", \")} aren't prefixed \\`${prefix}\\`; the SDK doesn't scope them, so they can collide with other blocks.`,\n });\n }\n return issues;\n}\n\nexport function checkEmailSafety(\n def: CustomBlockWorkingFile,\n rendered: RenderedState[],\n): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n const add = (\n ruleId: string,\n severity: CustomBlockIssue[\"severity\"],\n message: string,\n ) => issues.push({ ruleId, severity, message });\n\n for (const { state, html } of rendered) {\n let inStyle = false;\n let styleText = \"\";\n const parser = new Parser(\n {\n onopentag(name, attrs) {\n const style = attrs.style ?? \"\";\n if (FLEX_GRID.test(style))\n add(\"safety.flex-grid\", \"error\", FLEX_GRID_MESSAGE);\n if (POSITION.test(style))\n add(\"safety.position\", \"error\", POSITION_MESSAGE);\n if (CSS_VARIABLE.test(style))\n add(\"safety.css-variable\", \"warning\", CSS_VARIABLE_MESSAGE);\n if (backgroundIssue(style, attrs))\n add(\n \"safety.bg-image-fallback\",\n \"warning\",\n \"A background image needs a fallback colour (`background-color`, a colour in `background:`, or `bgcolor`); many clients block images.\",\n );\n if (name === \"svg\")\n add(\n \"safety.svg\",\n \"error\",\n \"Inline `<svg>` is stripped by Gmail and not drawn by Outlook; use a PNG `<img>`.\",\n );\n if (FORM_CONTROLS.has(name))\n add(\n \"safety.form\",\n \"warning\",\n `\\`<${name}>\\` doesn't work in most email clients (Gmail and Outlook disable forms); link to a page instead.`,\n );\n if (name === \"script\")\n add(\n \"safety.script\",\n \"error\",\n \"`<script>` is stripped by every email client.\",\n );\n if (\n name === \"link\" &&\n (attrs.rel ?? \"\").toLowerCase() === \"stylesheet\"\n )\n add(\n \"safety.external-stylesheet\",\n \"error\",\n \"External stylesheets are not loaded by email clients; use inline styles or `stylesheet`.\",\n );\n if (name === \"style\") {\n inStyle = true;\n styleText = \"\";\n add(\n \"safety.style-tag\",\n \"warning\",\n \"Move `<style>` rules into the definition's `stylesheet`, which the renderer places in `<mj-head>`.\",\n );\n }\n if (name === \"div\" && DIV_LAYOUT.test(style))\n add(\n \"safety.div-layout\",\n \"warning\",\n \"A `<div>` carrying width or float is unreliable in Outlook; lay out with tables.\",\n );\n if (name === \"img\") {\n if (!(\"alt\" in attrs))\n add(\n \"safety.img-alt\",\n \"warning\",\n 'Every `<img>` needs an `alt` attribute (`alt=\"\"` for decorative images), ideally bound to a field.',\n );\n if (!(\"width\" in attrs))\n add(\n \"safety.img-width\",\n \"warning\",\n \"Give `<img>` a `width` attribute; Outlook ignores CSS widths on images.\",\n );\n if ((attrs.src ?? \"\") === \"\")\n add(\n \"safety.empty-img-src\",\n \"warning\",\n `An \\`<img>\\` renders with an empty \\`src\\` in the \"${state}\" state; wrap it in \\`{% if <field> != blank %}\\` (Liquid treats an empty string as true, so a bare \\`{% if <field> %}\\` doesn't hide it).`,\n );\n }\n },\n ontext(text) {\n if (inStyle) styleText += text;\n },\n onclosetag(name) {\n if (name === \"style\") {\n inStyle = false;\n if (/@import\\b/i.test(stripCssComments(styleText)))\n add(\"safety.import\", \"error\", IMPORT_MESSAGE);\n issues.push(...cssLayoutIssues(styleText));\n }\n },\n },\n { decodeEntities: true },\n );\n parser.write(html);\n parser.end();\n }\n if (def.stylesheet) issues.push(...stylesheetIssues(def, def.stylesheet));\n\n const seen = new Set<string>();\n return issues.filter((i) => {\n const k = `${i.ruleId}\\0${i.path ?? \"\"}\\0${i.message}`;\n if (seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n}\n","// Accessibility over a custom block's rendered HTML, on every specimen state.\n// Built-in blocks get @templatical/quality's rules from structured fields; a\n// custom block is opaque HTML, so these read the markup itself. All are\n// warnings: they read inline markup only, and a block's colours or table use\n// can come from context they can't see.\n\nimport { Parser } from \"htmlparser2\";\nimport {\n getContrastRatio,\n isOpaqueHex,\n lintAccessibility,\n} from \"@templatical/quality\";\nimport {\n createDefaultTemplateContent,\n createParagraphBlock,\n} from \"@templatical/types\";\nimport type { RenderedState } from \"./render\";\nimport { declarations, stripCssUrlsAndStrings } from \"./safety\";\nimport type { CustomBlockIssue } from \"./types\";\n\nconst HEX = /#[0-9a-f]{3,8}\\b/i;\nconst PX = /^\\s*(\\d+(?:\\.\\d+)?)px/i;\n\ninterface Frame {\n color?: string;\n background?: string;\n fontSize?: number;\n}\n\n/** The colours and font size an element sets inline, over its parent's. */\nfunction frameFor(attrs: Record<string, string>, parent: Frame): Frame {\n const frame: Frame = { ...parent };\n if (attrs.bgcolor && isOpaqueHex(attrs.bgcolor))\n frame.background = attrs.bgcolor;\n for (const [prop, raw] of declarations(attrs.style ?? \"\")) {\n const value = raw.replace(/!important/i, \"\").trim();\n if (prop === \"color\" && isOpaqueHex(value)) frame.color = value;\n if (prop === \"background-color\" && isOpaqueHex(value))\n frame.background = value;\n if (prop === \"background\") {\n const hex = stripCssUrlsAndStrings(value).match(HEX)?.[0];\n if (hex && isOpaqueHex(hex)) frame.background = hex;\n }\n if (prop === \"font-size\") {\n const px = value.match(PX)?.[1];\n if (px) frame.fontSize = Number(px);\n }\n }\n return frame;\n}\n\n/** quality's own vague-link rule, run over the HTML as if it were a paragraph. */\nfunction vagueLinkMessages(html: string): string[] {\n const content = createDefaultTemplateContent();\n content.blocks = [createParagraphBlock({ content: html })];\n return lintAccessibility(content)\n .filter((i) => i.ruleId === \"a11y.link-vague-text\")\n .map((i) => i.message);\n}\n\nexport function checkAccessibility(\n rendered: RenderedState[],\n): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n const add = (ruleId: string, message: string) =>\n issues.push({ ruleId, severity: \"warning\", message });\n\n for (const { state, html } of rendered) {\n for (const message of vagueLinkMessages(html)) {\n add(\"a11y.link-vague-text\", message);\n }\n\n const frames: Frame[] = [{}];\n const tables: { presentation: boolean; hasHeader: boolean }[] = [];\n let link: { named: boolean } | null = null;\n\n const parser = new Parser(\n {\n onopentag(name, attrs) {\n frames.push(frameFor(attrs, frames[frames.length - 1]));\n if (name === \"table\") {\n const role = (attrs.role ?? \"\").toLowerCase();\n tables.push({\n presentation: role === \"presentation\" || role === \"none\",\n hasHeader: false,\n });\n }\n if (name === \"th\" && tables.length > 0) {\n tables[tables.length - 1].hasHeader = true;\n }\n if (name === \"a\") {\n link = {\n named:\n (attrs[\"aria-label\"] ?? \"\").trim() !== \"\" ||\n (attrs.title ?? \"\").trim() !== \"\",\n };\n }\n if (name === \"img\" && link && (attrs.alt ?? \"\").trim() !== \"\") {\n link.named = true;\n }\n },\n ontext(text) {\n const trimmed = text.trim();\n if (trimmed === \"\") return;\n if (link) link.named = true;\n const { color, background, fontSize } = frames[frames.length - 1];\n if (!color || !background) return;\n const required = (fontSize ?? 0) >= 24 ? 3 : 4.5;\n const ratio = getContrastRatio(color, background);\n if (Number.isNaN(ratio) || ratio >= required) return;\n add(\n \"a11y.contrast\",\n `Text in ${color} on ${background} has ${ratio.toFixed(2)}:1 contrast; it needs ${required}:1.`,\n );\n },\n onclosetag(name) {\n frames.pop();\n if (name === \"table\") {\n const table = tables.pop();\n if (table && !table.presentation && !table.hasHeader) {\n add(\n \"a11y.table-role\",\n 'A layout `<table>` needs `role=\"presentation\"`, or screen readers read it as a data table, cell by cell. A data table needs `<th>` headers instead.',\n );\n }\n }\n if (name === \"a\" && link) {\n if (!link.named) {\n add(\n \"a11y.link-no-name\",\n `A link has no text, \\`aria-label\\` or image \\`alt\\` in the \"${state}\" state, so a screen reader announces only \"link\".`,\n );\n }\n link = null;\n }\n },\n },\n { decodeEntities: true },\n );\n parser.write(html);\n parser.end();\n }\n\n const seen = new Set<string>();\n return issues.filter((i) => {\n const key = `${i.ruleId}\\0${i.message}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","","// Structural validation of a custom block definition: the generated\n// CustomBlockDefinition schema (dataSource excluded, see generate-schema.mjs),\n// the hand-written dataSourcePreview recipe schema, and the field-model checks\n// a JSON Schema can't express (duplicate keys, bounds that are ordered,\n// defaults within bounds). It takes a JSON working file or the object a\n// consumer's own module exports, `dataSource` function included, so the same\n// check can run in their CI over the definitions they ship.\n\nimport Ajv, { type ErrorObject } from \"ajv\";\nimport rawSchema from \"../../custom-block-schema.json\";\nimport type { CustomBlockCheckResult, CustomBlockIssue } from \"./types\";\n\nconst ajv = new Ajv({ allErrors: true, strict: false });\nconst validateDefinition = ajv.compile(rawSchema as object);\n\n// Owned by this package: the recipe is a template-tools concept, not part of\n// the SDK's block model, so it has no generated schema to drift from.\n// Reports the first problem only: a recipe is small, and one pointed message\n// reads better than a cascade of missing-property errors.\nconst validateRecipe = new Ajv({ allErrors: false, strict: false }).compile({\n type: \"object\",\n additionalProperties: false,\n required: [\"label\", \"request\", \"map\"],\n properties: {\n label: { type: \"string\", minLength: 1 },\n request: {\n type: \"object\",\n additionalProperties: false,\n required: [\"url\"],\n properties: {\n method: { enum: [\"GET\", \"POST\"] },\n url: { type: \"string\", minLength: 1 },\n headers: { type: \"object\", additionalProperties: { type: \"string\" } },\n body: { type: \"string\" },\n },\n },\n map: {\n type: \"object\",\n additionalProperties: { type: \"string\", minLength: 1 },\n },\n },\n});\n\nfunction schemaIssues(\n errors: ErrorObject[] | null | undefined,\n base = \"\",\n): CustomBlockIssue[] {\n return (errors ?? []).map((e) => ({\n ruleId: \"schema\",\n severity: \"error\",\n path: `${base}${e.instancePath}` || \"/\",\n message: `${base}${e.instancePath || \"\"} ${e.message ?? \"is invalid\"}${\n e.keyword === \"additionalProperties\"\n ? ` (\\`${(e.params as { additionalProperty: string }).additionalProperty}\\`)`\n : \"\"\n }`.trim(),\n }));\n}\n\ninterface FieldLike {\n key: string;\n type: string;\n default?: unknown;\n min?: number;\n max?: number;\n minItems?: number;\n maxItems?: number;\n options?: { value: string }[];\n fields?: FieldLike[];\n}\n\nconst isCount = (n: number | undefined) =>\n n === undefined || (Number.isInteger(n) && n >= 0);\n\nfunction fieldIssues(fields: FieldLike[], base: string): CustomBlockIssue[] {\n const issues: CustomBlockIssue[] = [];\n const seen = new Set<string>();\n fields.forEach((field, i) => {\n const path = `${base}/${i}`;\n if (seen.has(field.key)) {\n issues.push({\n ruleId: \"field.duplicate-key\",\n severity: \"error\",\n path,\n message: `Field key \\`${field.key}\\` is used twice; the second would overwrite the first's value.`,\n });\n }\n seen.add(field.key);\n if (\n field.type === \"number\" &&\n field.min !== undefined &&\n field.max !== undefined &&\n field.min > field.max\n ) {\n issues.push({\n ruleId: \"field.range\",\n severity: \"error\",\n path,\n message: `\\`${field.key}\\` has min ${field.min} above max ${field.max}, so no value is allowed.`,\n });\n }\n if (field.type === \"number\" && typeof field.default === \"number\") {\n const below = field.min !== undefined && field.default < field.min;\n const above = field.max !== undefined && field.default > field.max;\n if (below || above) {\n issues.push({\n ruleId: \"field.default-range\",\n severity: \"error\",\n path: `${path}/default`,\n message: `\\`${field.key}\\` defaults to ${field.default}, outside ${field.min ?? \"-∞\"}–${field.max ?? \"∞\"}.`,\n });\n }\n }\n if (\n field.type === \"select\" &&\n field.default !== undefined &&\n !(field.options ?? []).some((o) => o.value === field.default)\n ) {\n issues.push({\n ruleId: \"field.default-option\",\n severity: \"error\",\n path: `${path}/default`,\n message: `\\`${field.key}\\` defaults to \"${String(field.default)}\", which is not one of its options.`,\n });\n }\n if (field.type === \"repeatable\") {\n const { minItems, maxItems } = field;\n if (!isCount(minItems) || !isCount(maxItems)) {\n issues.push({\n ruleId: \"field.items-range\",\n severity: \"error\",\n path,\n message: `\\`${field.key}\\`'s minItems and maxItems must be whole numbers of 0 or more.`,\n });\n } else if (\n minItems !== undefined &&\n maxItems !== undefined &&\n minItems > maxItems\n ) {\n issues.push({\n ruleId: \"field.items-range\",\n severity: \"error\",\n path,\n message: `\\`${field.key}\\` has minItems ${minItems} above maxItems ${maxItems}, so no list length is allowed.`,\n });\n } else if (Array.isArray(field.default)) {\n const n = field.default.length;\n if (\n (minItems !== undefined && n < minItems) ||\n (maxItems !== undefined && n > maxItems)\n ) {\n issues.push({\n ruleId: \"field.default-items\",\n severity: \"error\",\n path: `${path}/default`,\n message: `\\`${field.key}\\` defaults to ${n} item(s), outside ${minItems ?? 0}–${maxItems ?? \"∞\"}; the palette would insert a list the editor won't allow.`,\n });\n }\n }\n if (field.fields) {\n issues.push(...fieldIssues(field.fields, `${path}/fields`));\n }\n }\n });\n return issues;\n}\n\n/**\n * A module's `dataSource` is `{ label, onFetch }` with a function, which the\n * schema can't describe (the generator hides functions). A JSON working file\n * can't hold a function at all, so there it is always an error that points at\n * `dataSourcePreview`.\n */\nfunction dataSourceIssues(dataSource: unknown): CustomBlockIssue[] {\n const ds = dataSource as { label?: unknown; onFetch?: unknown } | null;\n const issue = (message: string): CustomBlockIssue[] => [\n { ruleId: \"schema\", severity: \"error\", path: \"/dataSource\", message },\n ];\n if (ds === null || typeof ds !== \"object\" || Array.isArray(ds)) {\n return issue(\"`dataSource` must be an object with `label` and `onFetch`.\");\n }\n if (typeof ds.onFetch !== \"function\") {\n return issue(\n \"`dataSource.onFetch` must be a function. A JSON working file can't hold one: preview with `dataSourcePreview`, and write `dataSource` in the module at handoff.\",\n );\n }\n if (typeof ds.label !== \"string\" || ds.label === \"\") {\n return issue(\"`dataSource.label` must be a non-empty string.\");\n }\n return [];\n}\n\nexport function validateCustomBlockDefinition(\n data: unknown,\n): CustomBlockCheckResult {\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return {\n valid: false,\n issues: [\n {\n ruleId: \"schema\",\n severity: \"error\",\n path: \"/\",\n message: \"A custom block definition must be a JSON object.\",\n },\n ],\n };\n }\n const { dataSourcePreview, dataSource, ...definition } = data as Record<\n string,\n unknown\n >;\n const issues: CustomBlockIssue[] = [];\n if (dataSource !== undefined) issues.push(...dataSourceIssues(dataSource));\n if (!validateDefinition(definition)) {\n issues.push(...schemaIssues(validateDefinition.errors));\n }\n if (dataSourcePreview !== undefined && !validateRecipe(dataSourcePreview)) {\n issues.push(...schemaIssues(validateRecipe.errors, \"/dataSourcePreview\"));\n }\n if (issues.length === 0) {\n issues.push(\n ...fieldIssues((definition as { fields: FieldLike[] }).fields, \"/fields\"),\n );\n }\n return { valid: !issues.some((i) => i.severity === \"error\"), issues };\n}\n","// Static analysis of a custom block's Liquid template against its field\n// model. The engine options must equal the editor's renderer\n// (packages/editor/src/composables/useBlockRegistry.ts): a template that\n// renders here and fails there, or the reverse, makes every check below lie.\n// tests/custom-block-liquid.test.ts holds the two equal.\n\nimport { ForTag, Liquid, Output, Tag, type Template } from \"liquidjs\";\nimport type { CustomBlockIssue, CustomBlockWorkingFile } from \"./types\";\n\nexport function createLiquid(): Liquid {\n // `templates: {}` resolves include/render/layout against an empty in-memory\n // map, never the disk. Without it, Node's engine reads files relative to\n // the cwd: a recipe body could carry `.env` to the API host, and a template\n // would render here while the editor, which has no partials, fails.\n return new Liquid({\n strictVariables: false,\n strictFilters: false,\n templates: {},\n });\n}\n\n// `forloop` is Liquid's own loop object, never a field.\nconst LIQUID_GLOBALS = new Set([\"forloop\"]);\n\n// Tags that load another template file. A custom block's template and a\n// recipe are each one string, so nothing exists for them to load.\nconst FILE_TAGS = new Set([\"include\", \"render\", \"layout\"]);\n\n// Static analysis follows include/render into the partial by default, which\n// the engine refuses to load, so every analysis call skips partials. The\n// file-tag rule reports those tags instead.\nexport const NO_PARTIALS = { partials: false } as const;\n\n/** A tag's child templates, from liquidjs's static-analysis generator. */\nfunction childTemplates(template: Template): Template[] {\n if (!template.children) return [];\n const gen = template.children(false, true);\n let step = gen.next();\n while (!step.done) step = gen.next();\n return step.value ?? [];\n}\n\n/** Every template in the tree, depth-first, nested tags included. */\nfunction allTemplates(templates: Template[]): Template[] {\n return templates.flatMap((t) => [t, ...allTemplates(childTemplates(t))]);\n}\n\n/** Every `for` tag in the tree, nested ones included. */\nfunction forTags(templates: Template[]): ForTag[] {\n return allTemplates(templates).filter(\n (t): t is ForTag => t instanceof ForTag,\n );\n}\n\n/** Names of the file-loading tags in the tree, each once, in source order. */\nexport function fileTags(templates: Template[]): string[] {\n const names = allTemplates(templates)\n .filter((t): t is Tag => t instanceof Tag && FILE_TAGS.has(t.name))\n .map((t) => t.name);\n return [...new Set(names)];\n}\n\n/** How many `{{ }}` outputs in the tree reach the text without a `json` filter. */\nexport function outputsWithoutJson(templates: Template[]): number {\n return allTemplates(templates).filter(\n (t) =>\n t instanceof Output && !t.value.filters.some((f) => f.name === \"json\"),\n ).length;\n}\n\n/** The message for a template or recipe that uses a file-loading tag. */\nexport function fileTagMessage(name: string, where: string): string {\n return `\\`{% ${name} %}\\` loads another template file, and ${where} has none to load. Write its content inline instead.`;\n}\n\n/**\n * The field a loop iterates, when its collection is a bare identifier.\n * `for x in a.b` iterates a property of `a`, not `a`, so it has none.\n */\nfunction bareCollection(tag: ForTag): string | null {\n const c = tag.collection as {\n variable?: unknown;\n props?: Array<{ content?: unknown }>;\n };\n if (c.variable !== undefined || c.props?.length !== 1) return null;\n const name = c.props[0].content;\n return typeof name === \"string\" ? name : null;\n}\n\nexport function checkLiquid(def: CustomBlockWorkingFile): CustomBlockIssue[] {\n const engine = createLiquid();\n let parsed: Template[];\n try {\n parsed = engine.parse(def.template);\n } catch (err) {\n return [\n {\n ruleId: \"liquid.parse\",\n severity: \"error\",\n path: \"/template\",\n message: `The template does not parse: ${(err as Error).message}`,\n },\n ];\n }\n\n const issues: CustomBlockIssue[] = fileTags(parsed).map((name) => ({\n ruleId: \"liquid.file-tag\",\n severity: \"error\",\n path: \"/template\",\n message: fileTagMessage(name, \"a custom block\"),\n }));\n const fields = new Map(\n def.fields.map((f, i) => [f.key, { field: f, index: i }]),\n );\n\n const globals = new Set(\n engine\n .globalVariableSegmentsSync(def.template, NO_PARTIALS)\n .map((segments) => String(segments[0]))\n .filter((name) => !LIQUID_GLOBALS.has(name)),\n );\n for (const name of globals) {\n if (!fields.has(name)) {\n issues.push({\n ruleId: \"liquid.undefined-variable\",\n severity: \"error\",\n path: \"/template\",\n message: `The template reads \\`${name}\\`, which no field defines.`,\n });\n }\n }\n // A field only the data-source recipe reads (the product id it fetches by)\n // is in use; recipe Liquid that fails to parse is reported by checkRecipe.\n const recipeReads = new Set<string>();\n const request = def.dataSourcePreview?.request;\n for (const text of [request?.url, request?.body]) {\n if (text === undefined) continue;\n try {\n for (const [name] of engine.globalVariableSegmentsSync(\n text,\n NO_PARTIALS,\n )) {\n recipeReads.add(String(name));\n }\n } catch {\n /* reported by checkRecipe */\n }\n }\n for (const [key, { index }] of fields) {\n if (!globals.has(key) && !recipeReads.has(key)) {\n issues.push({\n ruleId: \"liquid.unused-field\",\n severity: \"warning\",\n path: `/fields/${index}`,\n message: `Field \\`${key}\\` is never read by the template or the data-source recipe.`,\n });\n }\n }\n\n // Each loop's body is checked against that loop's own collection, so two\n // loops may reuse one local name over different repeatables.\n for (const loop of forTags(parsed)) {\n const local = loop.variable;\n const collection = bareCollection(loop);\n if (collection === null) continue;\n const target = fields.get(collection)?.field;\n if (!target) continue;\n if (target.type !== \"repeatable\") {\n issues.push({\n ruleId: \"liquid.loop-not-repeatable\",\n severity: \"error\",\n path: \"/template\",\n message: `\\`{% for ${local} in ${collection} %}\\` iterates \\`${collection}\\`, which is a ${target.type} field, not a repeatable.`,\n });\n continue;\n }\n for (const [head, sub] of engine.variableSegmentsSync(\n loop.templates,\n NO_PARTIALS,\n )) {\n if (String(head) !== local || typeof sub !== \"string\") continue;\n if (!target.fields.some((f) => f.key === sub)) {\n issues.push({\n ruleId: \"liquid.unknown-item-key\",\n severity: \"error\",\n path: \"/template\",\n message: `\\`${local}.${sub}\\` reads \\`${sub}\\`, which \\`${collection}\\`'s items don't define.`,\n });\n }\n }\n }\n return issues;\n}\n","// The preview-only data-source recipe. The live bridge runs it from Node so\n// the page never sees a secret and no CORS applies; the agent generates the\n// shipped onFetch from the same recipe, so preview and production agree.\n//\n// These rules keep secrets contained, and each is load-bearing:\n// - `${env:NAME}` resolves only in header values;\n// - field values interpolate only into `url` and `body`;\n// - the Liquid engine loads no files, so `{% include %}` can't put a local\n// file (`.env`) into the request;\n// - the URL's scheme and host are literal, every field value is URL-encoded\n// (non-scalars as JSON), and the rendered URL must keep the recipe's\n// origin, so no field value can send the headers to another host.\n// A field value can never pull an env var into the request, and a secret can\n// never be moved into the URL or off to a host the author didn't write.\n\nimport {\n createLiquid,\n fileTagMessage,\n fileTags,\n NO_PARTIALS,\n outputsWithoutJson,\n} from \"./liquid\";\nimport type {\n CustomBlockIssue,\n CustomBlockWorkingFile,\n DataSourcePreview,\n} from \"./types\";\n\nconst ENV_REF = /\\$\\{env:([A-Za-z_][A-Za-z0-9_]*)\\}/g;\nconst LIQUID = /\\{\\{|\\{%/;\nconst UNSAFE_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n// A header-name word that names a credential. Whole words of the name split\n// on - and _, so X-Session-Locale and Accept-Keys don't match while X-API-Key,\n// X-Auth-Token and X-AccessToken do. `session` counts only before `id`.\nconst CREDENTIAL_WORD =\n /^(?:authorization|auth|cookie|key|apikey|password|credentials?|sessionid)$|(?:token|secret)$/;\n\nfunction namesCredential(header: string): boolean {\n const words = header.toLowerCase().split(/[-_]/);\n return words.some(\n (w, i) =>\n CREDENTIAL_WORD.test(w) || (w === \"session\" && words[i + 1] === \"id\"),\n );\n}\n\nconst HOST_TEMPLATE_ERROR =\n \"The request URL's scheme and host must be literal; Liquid may only follow the first `/` after the host.\";\n\n/**\n * Where the URL's scheme and authority end: the first `/`, `?` or `#` after\n * `://`, or the whole string when there is no `://`. Anything before this\n * index decides the origin, so it must hold no Liquid.\n */\nfunction authorityEnd(url: string): number {\n const scheme = url.indexOf(\"://\");\n if (scheme < 0) return url.length;\n const rest = url.slice(scheme + 3).search(/[/?#]/);\n return rest < 0 ? url.length : scheme + 3 + rest;\n}\n\n/** The recipe URL's origin, or null when Liquid can reach it. */\nfunction literalOrigin(url: string): string | null {\n const prefix = url.slice(0, authorityEnd(url));\n if (LIQUID.test(prefix)) return null;\n try {\n return new URL(prefix).origin;\n } catch {\n return null;\n }\n}\n\nfunction encodeValue(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"string\" || typeof value === \"number\") {\n return encodeURIComponent(String(value));\n }\n return encodeURIComponent(JSON.stringify(value));\n}\n\nexport function getPath(value: unknown, path: string): unknown {\n const parts = path\n .replace(/\\[(\\d+)\\]/g, \".$1\")\n .split(\".\")\n .filter(Boolean);\n let cur: unknown = value;\n for (const part of parts) {\n if (cur === null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[part];\n }\n return cur;\n}\n\nexport function checkRecipe(def: CustomBlockWorkingFile): CustomBlockIssue[] {\n const r = def.dataSourcePreview;\n if (!r) return [];\n const issues: CustomBlockIssue[] = [];\n const keys = new Set(def.fields.map((f) => f.key));\n const add =\n (severity: CustomBlockIssue[\"severity\"]) =>\n (ruleId: string, path: string, message: string) =>\n issues.push({\n ruleId,\n severity,\n path: `/dataSourcePreview${path}`,\n message,\n });\n const err = add(\"error\");\n const warn = add(\"warning\");\n const engine = createLiquid();\n\n for (const [part, text] of [\n [\"/request/url\", r.request.url],\n [\"/request/body\", r.request.body],\n ] as const) {\n if (text === undefined) continue;\n if (new RegExp(ENV_REF.source).test(text)) {\n err(\n \"recipe.env-outside-headers\",\n part,\n \"`${env:…}` is resolved only in header values; keep secrets out of the URL and body.\",\n );\n }\n try {\n for (const [name] of engine.globalVariableSegmentsSync(\n text,\n NO_PARTIALS,\n )) {\n if (!keys.has(String(name)))\n err(\n \"recipe.undefined-variable\",\n part,\n `The recipe reads \\`${String(name)}\\`, which no field defines.`,\n );\n }\n // The engine can't read files, so such a recipe would fail at fetch\n // time; refusing it here keeps the bridge from running it at all.\n const parsed = engine.parse(text);\n for (const name of fileTags(parsed)) {\n err(\"recipe.file-tag\", part, fileTagMessage(name, \"a recipe\"));\n }\n // Body values go in raw, so a quote typed into a field ends the string\n // early. `| json` quotes and escapes the value.\n if (\n part === \"/request/body\" &&\n /^\\s*[[{]/.test(text) &&\n outputsWithoutJson(parsed) > 0\n ) {\n warn(\n \"recipe.body-unescaped\",\n part,\n \"A JSON body interpolates a value without `| json`, so a quote in it breaks the JSON. Write `{{ key | json }}`, which adds the quotes.\",\n );\n }\n } catch (e) {\n err(\n \"recipe.undefined-variable\",\n part,\n `Does not parse: ${(e as Error).message}`,\n );\n }\n }\n // Without `://` the URL is not absolute, which recipe.url below reports.\n if (\n r.request.url.includes(\"://\") &&\n LIQUID.test(r.request.url.slice(0, authorityEnd(r.request.url)))\n ) {\n err(\"recipe.url-host-template\", \"/request/url\", HOST_TEMPLATE_ERROR);\n }\n const probeUrl = replaceLiquid(r.request.url);\n let parses = true;\n try {\n new URL(probeUrl);\n } catch {\n parses = false;\n }\n if (!/^https?:\\/\\//i.test(probeUrl) || !parses) {\n err(\n \"recipe.url\",\n \"/request/url\",\n \"The request URL must be an absolute http(s) URL.\",\n );\n }\n if (r.request.body !== undefined && (r.request.method ?? \"GET\") !== \"POST\") {\n err(\n \"recipe.body-without-post\",\n \"/request/body\",\n 'A `body` is sent only with `\"method\": \"POST\"`; a GET request can\\'t carry one.',\n );\n }\n for (const [name, value] of Object.entries(r.request.headers ?? {})) {\n if (LIQUID.test(value)) {\n err(\n \"recipe.field-in-header\",\n `/request/headers/${name}`,\n `Header \\`${name}\\` interpolates a field; headers take only \\`\\${env:NAME}\\`.`,\n );\n } else if (\n namesCredential(name) &&\n !new RegExp(ENV_REF.source).test(value)\n ) {\n err(\n \"recipe.literal-secret\",\n `/request/headers/${name}`,\n `Header \\`${name}\\` holds a literal credential. Use \\`\\${env:NAME}\\` and export the variable instead.`,\n );\n }\n }\n for (const field of Object.keys(r.map)) {\n if (!keys.has(field))\n err(\n \"recipe.unknown-map-key\",\n `/map/${field}`,\n `\\`map\\` writes \\`${field}\\`, which no field defines.`,\n );\n }\n return issues;\n}\n\nexport interface RecipeOptions {\n env?: Record<string, string | undefined>;\n fetch?: typeof fetch;\n timeoutMs?: number;\n}\nexport type RecipeResult =\n | {\n ok: true;\n status: number;\n values: Record<string, unknown>;\n unmapped: string[];\n }\n | { ok: false; status?: number; error: string };\n\nexport async function runRecipe(\n recipe: DataSourcePreview,\n fieldValues: Record<string, unknown>,\n {\n env = process.env,\n fetch: doFetch = fetch,\n timeoutMs = 10_000,\n }: RecipeOptions = {},\n): Promise<RecipeResult> {\n const expectedOrigin = literalOrigin(recipe.request.url);\n if (expectedOrigin === null) return { ok: false, error: HOST_TEMPLATE_ERROR };\n const engine = createLiquid();\n const encoded = Object.fromEntries(\n Object.entries(fieldValues).map(([k, v]) => [k, encodeValue(v)]),\n );\n let url: string;\n let body: string | undefined;\n try {\n url = String(await engine.parseAndRender(recipe.request.url, encoded));\n body =\n recipe.request.body === undefined\n ? undefined\n : String(await engine.parseAndRender(recipe.request.body, fieldValues));\n } catch (e) {\n return {\n ok: false,\n error: `The request template does not render: ${(e as Error).message}`,\n };\n }\n\n let renderedOrigin: string | null = null;\n try {\n renderedOrigin = new URL(url).origin;\n } catch {\n /* reported below */\n }\n // Unreachable while the URL authority refuses Liquid and every value is\n // encoded; it stops the proxy being repointed if either guard regresses.\n /* v8 ignore start */\n if (renderedOrigin !== expectedOrigin) {\n return {\n ok: false,\n error: `The rendered URL's origin (${renderedOrigin ?? \"unparseable\"}) differs from the recipe's (${expectedOrigin}); no request was made.`,\n };\n }\n /* v8 ignore stop */\n\n const secrets: string[] = [];\n const headers: Record<string, string> = {};\n for (const [name, template] of Object.entries(recipe.request.headers ?? {})) {\n let missing: string | undefined;\n headers[name] = template.replace(ENV_REF, (_, variable: string) => {\n const value = env[variable];\n if (value === undefined || value === \"\") {\n missing ??= variable;\n return \"\";\n }\n secrets.push(value);\n return value;\n });\n if (missing) {\n return {\n ok: false,\n error: `Environment variable ${missing} is not set. Export it in the shell that runs the live server.`,\n };\n }\n }\n const redact = (s: string) =>\n secrets.reduce((acc, secret) => acc.split(secret).join(\"***\"), s);\n const redactDeep = (v: unknown): unknown => {\n if (typeof v === \"string\") return redact(v);\n if (Array.isArray(v)) return v.map(redactDeep);\n if (v !== null && typeof v === \"object\") {\n return Object.fromEntries(\n Object.entries(v).map(([k, x]) => [k, redactDeep(x)]),\n );\n }\n return v;\n };\n const method = recipe.request.method ?? \"GET\";\n\n try {\n const res = await doFetch(url, {\n method,\n headers,\n body,\n redirect: \"manual\",\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (res.status >= 300 && res.status < 400) {\n const location = res.headers.get(\"location\") ?? \"(none)\";\n return {\n ok: false,\n status: res.status,\n error: redact(\n `${method} ${url} redirected (${res.status}) to ${location}. Redirects are not followed; point the recipe at the final URL.`,\n ),\n };\n }\n const text = await res.text();\n if (!res.ok) {\n return {\n ok: false,\n status: res.status,\n error: `${redact(`${method} ${url} returned ${res.status}: `)}${redact(text).slice(0, 200)}`,\n };\n }\n let json: unknown;\n try {\n json = JSON.parse(text);\n } catch {\n return {\n ok: false,\n status: res.status,\n error: redact(`${method} ${url} did not return JSON.`),\n };\n }\n const values: Record<string, unknown> = {};\n const unmapped: string[] = [];\n for (const [field, path] of Object.entries(recipe.map)) {\n if (UNSAFE_KEYS.has(field)) continue;\n const v = getPath(json, path);\n if (v === undefined) unmapped.push(field);\n else values[field] = redactDeep(v);\n }\n return { ok: true, status: res.status, values, unmapped };\n } catch (e) {\n return {\n ok: false,\n error: redact(`${method} ${url} failed: ${(e as Error).message}`),\n };\n }\n}\n\n/** Replaces each `{{…}}` / `{%…%}` span with \"x\" in one linear pass; an unterminated opener swallows the rest. */\nexport function replaceLiquid(text: string): string {\n const MISSING = Infinity;\n const next = (needle: string, from: number) => {\n const at = text.indexOf(needle, from);\n return at === -1 ? MISSING : at;\n };\n let out = \"\";\n let i = 0;\n // Each opener is searched for again only once the cursor passes its cached\n // position, so an opener that never recurs is scanned for once, not per span.\n let a = next(\"{{\", 0);\n let b = next(\"{%\", 0);\n for (;;) {\n if (a < i) a = next(\"{{\", i);\n if (b < i) b = next(\"{%\", i);\n const open = Math.min(a, b);\n if (open === MISSING) return out + text.slice(i);\n out += text.slice(i, open);\n const close = text.indexOf(open === a ? \"}}\" : \"%}\", open + 2);\n if (close === -1) return out + \"x\";\n out += \"x\";\n i = close + 2;\n }\n}\n","// The specimen sheet: one instance of the block per state that commonly\n// breaks a custom block — defaults, every optional cleared, long copy,\n// repeatables at their bounds, booleans inverted — so the live preview shows\n// the edge cases without the user thinking to try them.\n\nimport { escapeHtml } from \"@templatical/renderer\";\nimport {\n createCustomBlock,\n createDefaultTemplateContent,\n createParagraphBlock,\n type Block,\n type CustomBlockDefinition,\n type CustomBlockField,\n type TemplateContent,\n} from \"@templatical/types\";\nimport type { CustomBlockWorkingFile } from \"./types\";\n\nexport type SpecimenState =\n \"defaults\" | \"empty\" | \"long\" | \"min-items\" | \"max-items\" | \"flipped\";\nexport const SPECIMEN_STATES: readonly SpecimenState[] = [\n \"defaults\",\n \"empty\",\n \"long\",\n \"min-items\",\n \"max-items\",\n \"flipped\",\n];\nexport interface SpecimenInstance {\n state: SpecimenState;\n fieldValues: Record<string, unknown>;\n}\nexport const DEFAULT_MAX_ITEMS = 6;\n\ntype ItemField = Exclude<CustomBlockField, { type: \"repeatable\" }>;\ntype Repeatable = Extract<CustomBlockField, { type: \"repeatable\" }>;\n\n// Same fallbacks as @templatical/types' createCustomBlock, which keeps its own\n// copy private; the \"defaults\" test holds the two equal.\nfunction fieldDefault(f: CustomBlockField): unknown {\n if (f.default !== undefined) return structuredClone(f.default);\n if (f.type === \"repeatable\") return [];\n if (f.type === \"boolean\") return false;\n if (f.type === \"number\") return 0;\n return \"\";\n}\n\nfunction itemFrom(fields: ItemField[]): Record<string, unknown> {\n return Object.fromEntries(fields.map((f) => [f.key, fieldDefault(f)]));\n}\n\nfunction items(f: Repeatable, n: number): Record<string, unknown>[] {\n const seeds =\n Array.isArray(f.default) && f.default.length > 0\n ? f.default\n : [itemFrom(f.fields)];\n return Array.from({ length: n }, (_, i) =>\n structuredClone(seeds[i % seeds.length]),\n );\n}\n\nfunction lengthen(f: CustomBlockField, v: unknown): unknown {\n if (f.type === \"text\" || f.type === \"textarea\") {\n const base = (typeof v === \"string\" && v) || f.placeholder || f.label;\n return [base, base, base].join(\" \");\n }\n if (f.type === \"number\") return f.max ?? v;\n if (f.type === \"repeatable\" && Array.isArray(v)) {\n return v.map((item: Record<string, unknown>) =>\n Object.fromEntries(\n f.fields.map((s) => [s.key, lengthen(s, item[s.key])]),\n ),\n );\n }\n return v;\n}\n\nfunction clear(f: CustomBlockField, v: unknown): unknown {\n if (f.required) return v;\n if (f.type === \"boolean\") return false;\n if (f.type === \"number\") return null;\n if (f.type === \"repeatable\") return items(f, f.minItems ?? 0);\n return \"\";\n}\n\nexport function buildSpecimen(def: CustomBlockWorkingFile): SpecimenInstance[] {\n const defaults = createCustomBlock(def as CustomBlockDefinition).fieldValues;\n const map = (fn: (f: CustomBlockField, v: unknown) => unknown) =>\n Object.fromEntries(\n def.fields.map((f) => [f.key, fn(f, structuredClone(defaults[f.key]))]),\n );\n\n const out: SpecimenInstance[] = [\n { state: \"defaults\", fieldValues: structuredClone(defaults) },\n { state: \"empty\", fieldValues: map(clear) },\n { state: \"long\", fieldValues: map(lengthen) },\n ];\n const repeatables = def.fields.filter(\n (f): f is Repeatable => f.type === \"repeatable\",\n );\n if (repeatables.length > 0) {\n const sized = (pick: (f: Repeatable) => number) => ({\n ...structuredClone(defaults),\n ...Object.fromEntries(repeatables.map((f) => [f.key, items(f, pick(f))])),\n });\n out.push({\n state: \"min-items\",\n fieldValues: sized((f) => f.minItems ?? 0),\n });\n // Never below minItems: without a maxItems, the fallback can sit under\n // the minimum, and the editor never lets a list get that short.\n out.push({\n state: \"max-items\",\n fieldValues: sized((f) =>\n Math.max(f.minItems ?? 0, f.maxItems ?? DEFAULT_MAX_ITEMS),\n ),\n });\n }\n const booleans = def.fields.filter((f) => f.type === \"boolean\");\n if (booleans.length > 0) {\n out.push({\n state: \"flipped\",\n fieldValues: {\n ...structuredClone(defaults),\n ...Object.fromEntries(booleans.map((f) => [f.key, !defaults[f.key]])),\n },\n });\n }\n return out;\n}\n\n/** What running the data-source recipe for the live preview produced. */\nexport type FetchedSpecimen =\n | { ok: true; fieldValues: Record<string, unknown> }\n | { ok: false; error: string };\n\n/**\n * The live preview's \"fetched\" state: the block filled from the real\n * endpoint and marked fetched, so read-only fields lock as they do in\n * production. A failed request puts its error on the label instead.\n */\nexport function fetchedSpecimenBlocks(\n def: CustomBlockWorkingFile,\n fetched: FetchedSpecimen,\n): Block[] {\n const id = \"specimen-fetched\";\n if (!fetched.ok) {\n return [\n createParagraphBlock({\n id: `${id}-label`,\n content: `<p><strong>Specimen:</strong> fetched. The request failed: ${escapeHtml(fetched.error)}</p>`,\n }),\n ];\n }\n return [\n createParagraphBlock({\n id: `${id}-label`,\n content: \"<p><strong>Specimen:</strong> fetched</p>\",\n }),\n {\n ...createCustomBlock(def as CustomBlockDefinition),\n id,\n fieldValues: fetched.fieldValues,\n dataSourceFetched: true,\n },\n ];\n}\n\n/** The id each state's custom block gets in the specimen sheet; its label is `${id}-label`. */\nexport function specimenBlockId(state: SpecimenState): string {\n return `specimen-${state}`;\n}\n\nexport function buildSpecimenTemplate(\n def: CustomBlockWorkingFile,\n host?: TemplateContent,\n only?: SpecimenState,\n): TemplateContent {\n const content = host ? structuredClone(host) : createDefaultTemplateContent();\n for (const instance of buildSpecimen(def)) {\n if (only && instance.state !== only) continue;\n // Fixed ids, not fresh ones: the sheet is rebuilt on every reload and\n // page load, and a note left on a block keys on its id. A random id\n // would orphan every note at the next reload.\n const id = specimenBlockId(instance.state);\n content.blocks.push(\n createParagraphBlock({\n id: `${id}-label`,\n content: `<p><strong>Specimen:</strong> ${instance.state}</p>`,\n }),\n {\n ...createCustomBlock(def as CustomBlockDefinition),\n id,\n fieldValues: instance.fieldValues,\n },\n );\n }\n return content;\n}\n","// Renders a custom block the way the editor and the renderer do: Liquid over\n// fieldValues for the HTML, then renderToMjml with that HTML and the\n// definition's stylesheet, which the renderer emits once into <mj-head>.\n\nimport { renderToMjml } from \"@templatical/renderer\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport { createLiquid } from \"./liquid\";\nimport {\n buildSpecimen,\n buildSpecimenTemplate,\n type SpecimenInstance,\n type SpecimenState,\n} from \"./specimen\";\nimport type { CustomBlockWorkingFile } from \"./types\";\n\nexport interface RenderedState {\n state: SpecimenState;\n html: string;\n}\n\nexport async function renderStates(\n def: CustomBlockWorkingFile,\n instances: SpecimenInstance[] = buildSpecimen(def),\n): Promise<RenderedState[]> {\n const liquid = createLiquid();\n return Promise.all(\n instances.map(async (i) => ({\n state: i.state,\n html: String(await liquid.parseAndRender(def.template, i.fieldValues)),\n })),\n );\n}\n\nexport async function renderSpecimenMjml(\n def: CustomBlockWorkingFile,\n template: TemplateContent = buildSpecimenTemplate(def),\n): Promise<string> {\n const liquid = createLiquid();\n return renderToMjml(template, {\n renderCustomBlock: async (block) =>\n String(await liquid.parseAndRender(def.template, block.fieldValues)),\n getCustomBlockStylesheet: (customType) =>\n customType === def.type ? def.stylesheet : undefined,\n });\n}\n","// The full `custom-block validate` pipeline, as a library function. Stages\n// short-circuit where a later stage would only report noise: no Liquid or\n// render checks on a structurally broken definition, no render on a template\n// that doesn't parse or loads another file, and no lint on one that fails to\n// render.\n\nimport { checkAccessibility } from \"./a11y\";\nimport { validateCustomBlockDefinition } from \"./definition\";\nimport { checkLiquid } from \"./liquid\";\nimport { checkRecipe } from \"./recipe\";\nimport { renderSpecimenMjml, renderStates, type RenderedState } from \"./render\";\nimport { checkEmailSafety } from \"./safety\";\nimport type {\n CustomBlockCheckResult,\n CustomBlockIssue,\n CustomBlockWorkingFile,\n} from \"./types\";\n\nexport interface CheckCustomBlockOptions {\n compileMjml?: (mjml: string) => Promise<{ errors: unknown[] }>;\n}\n\nconst finish = (issues: CustomBlockIssue[]): CustomBlockCheckResult => ({\n valid: !issues.some((i) => i.severity === \"error\"),\n issues,\n});\n\nexport async function checkCustomBlock(\n data: unknown,\n { compileMjml }: CheckCustomBlockOptions = {},\n): Promise<CustomBlockCheckResult> {\n const structural = validateCustomBlockDefinition(data);\n if (!structural.valid) return structural;\n const def = data as CustomBlockWorkingFile;\n\n const issues = [...structural.issues, ...checkRecipe(def)];\n const liquid = checkLiquid(def);\n issues.push(...liquid);\n if (\n liquid.some(\n (i) => i.ruleId === \"liquid.parse\" || i.ruleId === \"liquid.file-tag\",\n )\n )\n return finish(issues);\n\n // A template can parse and still throw while rendering; that is a finding\n // about the definition, not a crash of the check.\n let rendered: RenderedState[];\n try {\n rendered = await renderStates(def);\n } catch (err) {\n issues.push({\n ruleId: \"liquid.render\",\n severity: \"error\",\n path: \"/template\",\n message: `The template does not render: ${(err as Error).message}`,\n });\n return finish(issues);\n }\n issues.push(...checkEmailSafety(def, rendered));\n issues.push(...checkAccessibility(rendered));\n const mjml = await renderSpecimenMjml(def);\n // The renderer trims the stylesheet before emitting it.\n const stylesheet = def.stylesheet?.trim();\n if (stylesheet && !mjml.includes(stylesheet)) {\n issues.push({\n ruleId: \"render.stylesheet-missing\",\n severity: \"error\",\n message: \"The stylesheet did not reach <mj-head> in the rendered MJML.\",\n });\n }\n if (compileMjml) {\n for (const e of (await compileMjml(mjml)).errors) {\n const o = e as { formattedMessage?: string; message?: string };\n issues.push({\n ruleId: \"render.mjml\",\n severity: \"warning\",\n message: o.formattedMessage ?? o.message ?? String(e),\n });\n }\n }\n return finish(issues);\n}\n"],"mappings":";;;;;;;;;ACyBA,MAAa,SAASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { d as schema, f as validateTemplate, l as validateCustomBlockDefinition, r as SPECIMEN_STATES, t as checkCustomBlock, u as runQualityLint } from "./custom-block-sAM61QPU.js";
2
- import { n as getColumnCount, t as applyOperation } from "./src-Ce1Oh8m_.js";
1
+ import { d as schema, f as validateTemplate, l as validateCustomBlockDefinition, r as SPECIMEN_STATES, t as checkCustomBlock, u as runQualityLint } from "./custom-block-CsZN9qMM.js";
2
+ import { n as getColumnCount, t as applyOperation } from "./src-SZZkttBC.js";
3
3
  export { SPECIMEN_STATES, applyOperation, checkCustomBlock, getColumnCount, runQualityLint, schema, validateCustomBlockDefinition, validateTemplate };
@@ -1,6 +1,6 @@
1
1
  import { Server } from "node:http";
2
2
  //#region src/live/index.d.ts
3
- export declare const EDITOR_VERSION = "0.47.0";
3
+ export declare const EDITOR_VERSION = "0.48.0";
4
4
  export declare const DEFAULT_PORT = 4747;
5
5
  export declare const WORKING_DIR = ".templatical";
6
6
  export declare const PID_FILE: string;
@@ -1,4 +1,4 @@
1
- import { a as buildSpecimenTemplate, c as runRecipe, f as validateTemplate, i as buildSpecimen, l as validateCustomBlockDefinition, o as fetchedSpecimenBlocks, s as checkRecipe } from "../custom-block-sAM61QPU.js";
1
+ import { a as buildSpecimenTemplate, c as runRecipe, f as validateTemplate, i as buildSpecimen, l as validateCustomBlockDefinition, o as fetchedSpecimenBlocks, s as checkRecipe } from "../custom-block-CsZN9qMM.js";
2
2
  import { createDefaultTemplateContent } from "@templatical/types";
3
3
  import { createServer } from "node:http";
4
4
  import { spawn } from "node:child_process";
@@ -7,7 +7,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  //#region src/live/index.ts
9
9
  const here = dirname(fileURLToPath(import.meta.url));
10
- const EDITOR_VERSION = "0.47.0";
10
+ const EDITOR_VERSION = "0.48.0";
11
11
  const DEFAULT_PORT = 4747;
12
12
  const WORKING_DIR = ".templatical";
13
13
  const PID_FILE = join(WORKING_DIR, "live-server.pid");
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/live/index.ts"],"sourcesContent":["// The local live-preview bridge.\n//\n// It serves live/index.html (which loads the REAL Templatical editor from the\n// CDN), streams agent-driven template updates to the page over Server-Sent\n// Events, and buffers the page's hand-edits so the caller can detect divergence\n// (a user edit in the browser) before overwriting.\n//\n// This package's own `live` CLI command drives it today, over localhost HTTP.\n// It stays side-effect free at import time and writes nothing to stdout so\n// that a future stdio-based caller — an MCP server reserving stdout for\n// JSON-RPC — can be added without auditing this module first: a stray write\n// here would corrupt that protocol. Keep all human-facing output in the\n// callers (cli/output.ts is the CLI's own single writer;\n// tests/cli-output-discipline.test.ts enforces that no other module under\n// src/ touches stdout).\n\nimport { createServer, type Server, type ServerResponse } from \"node:http\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n createDefaultTemplateContent,\n type TemplateContent,\n} from \"@templatical/types\";\nimport {\n buildSpecimen,\n buildSpecimenTemplate,\n checkRecipe,\n runRecipe,\n validateCustomBlockDefinition,\n type CustomBlockWorkingFile,\n} from \"../custom-block\";\nimport {\n fetchedSpecimenBlocks,\n type FetchedSpecimen,\n} from \"../custom-block/specimen\";\nimport { validateTemplate } from \"../validate\";\n\nconst here = dirname(fileURLToPath(import.meta.url));\n\n// The CDN editor version the live harness loads. It is PINNED to this package's\n// schema version: schema.json is generated from @templatical/types, and types +\n// editor bump in lockstep (changesets fixed group), so the editor at this\n// version has the same block model the schema describes.\n//\n// Kept in step automatically by scripts/sync-pins.mjs at release time.\n// tests/cdn-pin.test.ts is the safety net that fails CI if it ever drifts.\nexport const EDITOR_VERSION = \"0.47.0\";\n\nexport const DEFAULT_PORT = 4747;\nexport const WORKING_DIR = \".templatical\";\nexport const PID_FILE = join(WORKING_DIR, \"live-server.pid\");\n\n// Resolved from this module rather than the cwd so it works identically from\n// src/live/ (tests) and dist/live/ (published) — both sit two levels under the\n// package root, where live/index.html ships.\nconst HARNESS_FILE = resolve(here, \"../../live/index.html\");\n\n// --------------------------------------------------------------------------\n// Pure helpers\n// --------------------------------------------------------------------------\n\n/** Order-insensitive structural equality for parsed-JSON template content. */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\") return false;\n const aArr = Array.isArray(a);\n if (aArr !== Array.isArray(b)) return false;\n if (aArr) {\n const aList = a as unknown[];\n const bList = b as unknown[];\n if (aList.length !== bList.length) return false;\n for (let i = 0; i < aList.length; i++) {\n if (!deepEqual(aList[i], bList[i])) return false;\n }\n return true;\n }\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const ak = Object.keys(aObj);\n const bk = Object.keys(bObj);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!Object.prototype.hasOwnProperty.call(bObj, k)) return false;\n if (!deepEqual(aObj[k], bObj[k])) return false;\n }\n return true;\n}\n\n/** Read + parse the working file, or return null when it is absent/unparseable. */\nexport function readWorkingFile(path: string): unknown {\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf8\"));\n } catch {\n return null;\n }\n}\n\n/** List the working templates in a project's `.templatical/` folder, by name. */\nexport function listWorkingFiles(cwd: string): string[] {\n const dir = resolve(cwd, WORKING_DIR);\n if (!existsSync(dir)) return [];\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".json\"))\n .sort();\n}\n\nasync function readJsonBody(req: NodeJS.ReadableStream): Promise<\n | {\n content?: unknown;\n baseline?: boolean;\n blockId?: string;\n parentBlockId?: unknown;\n blockType?: unknown;\n label?: unknown;\n text?: string;\n consumeAnnotations?: boolean;\n fieldValues?: unknown;\n }\n | undefined\n> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n const raw = Buffer.concat(chunks).toString(\"utf8\");\n return raw ? JSON.parse(raw) : undefined;\n}\n\n/**\n * Open a URL in the user's default browser — best-effort and cross-platform.\n * A silent no-op where there's no browser (headless / CI / sandbox); callers\n * always surface the URL too, so nothing is lost when this does nothing.\n */\nexport function openBrowser(url: string): void {\n const [cmd, cmdArgs]: [string, string[]] =\n process.platform === \"darwin\"\n ? [\"open\", [url]]\n : process.platform === \"win32\"\n ? [\"cmd\", [\"/c\", \"start\", \"\", url]]\n : [\"xdg-open\", [url]];\n try {\n const child = spawn(cmd, cmdArgs, { stdio: \"ignore\", detached: true });\n child.on(\"error\", () => {}); // no browser available — ignore\n child.unref();\n } catch {\n /* ignore — the URL is surfaced by the caller regardless */\n }\n}\n\n// --------------------------------------------------------------------------\n// Pidfile helpers (used by this package's own `live` CLI command; a future\n// stdio-based MCP server would own its process lifecycle directly and would\n// not need them)\n// --------------------------------------------------------------------------\n\nexport type LiveMode = \"template\" | \"custom-block\";\n\nexport interface PidfileInfo {\n pid: number;\n port: number;\n /** Absent in a pidfile from a template-only bridge; read as \"template\". */\n mode?: LiveMode;\n /** The absolute path the server is serving. */\n path?: string;\n /** Custom-block mode: the absolute --host template, when one was given. */\n host?: string;\n /** Custom-block mode with --fetch: the --values the recipe runs with. */\n fetch?: Record<string, unknown>;\n}\n\n/** How the data-source request for the fetched specimen state went. */\nexport type FetchSummary =\n { ok: true; unmapped: string[] } | { ok: false; error: string };\n\n/** What a reload pushed, and why it pushed nothing when `ok` is false. */\nexport interface ReloadResult {\n ok: boolean;\n clients: number;\n consumed: boolean;\n mode: LiveMode;\n error?: string;\n /** With the `fetch` option: the request behind the fetched state. */\n fetch?: FetchSummary;\n}\n\nexport function pidfilePath(cwd: string): string {\n return resolve(cwd, PID_FILE);\n}\n\nexport function readPidfile(cwd: string): PidfileInfo | null {\n const p = pidfilePath(cwd);\n if (!existsSync(p)) return null;\n try {\n return JSON.parse(readFileSync(p, \"utf8\")) as PidfileInfo;\n } catch {\n return null;\n }\n}\n\nexport function processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * A note the user left in the browser, against a block or the template as a\n * whole. Delivered on GET /content - the call the agent already makes before\n * every change - so notes need no polling and no separate endpoint to drain.\n */\nexport interface Annotation {\n id: string;\n /** null for a note about the template rather than one block. */\n blockId: string | null;\n /** The next data-block-id above the target. null at the top level and for a template note. */\n parentBlockId: string | null;\n blockType: string | null;\n /** Snapshot of the block's text at save time, already truncated. */\n label: string;\n text: string;\n createdAt: number;\n}\n\nconst NOTE_TEXT_MAX = 2000;\nconst NOTE_LABEL_MAX = 80;\nconst NOTE_BLOCK_TYPE_MAX = 40;\nconst NOTE_QUEUE_MAX = 40;\n\nfunction readNoteText(value: unknown): { text: string } | { error: string } {\n const text = typeof value === \"string\" ? value.trim() : \"\";\n if (!text) return { error: \"An annotation needs non-empty `text`.\" };\n if (text.length > NOTE_TEXT_MAX) {\n return { error: \"A note can be at most 2000 characters.\" };\n }\n return { text };\n}\n\nfunction readOptionalId(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\n\nfunction readBlockType(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n return value.trim().slice(0, NOTE_BLOCK_TYPE_MAX);\n}\n\nfunction readLabel(value: unknown): string {\n if (typeof value !== \"string\") return \"\";\n return value.replace(/\\s+/g, \" \").trim().slice(0, NOTE_LABEL_MAX);\n}\n\nfunction annotationIdFromPath(pathname: string): string | null {\n const prefix = \"/annotations/\";\n if (!pathname.startsWith(prefix)) return null;\n const raw = pathname.slice(prefix.length);\n if (!raw || raw.includes(\"/\")) return null;\n try {\n return decodeURIComponent(raw);\n } catch {\n return null;\n }\n}\n\nfunction loopbackNames(port: number): string[] {\n return [`localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`];\n}\n\nfunction noteNotFound(res: ServerResponse): void {\n res.writeHead(404, { \"content-type\": \"text/plain\" });\n res.end(\"No note with that id.\");\n}\n\n// --------------------------------------------------------------------------\n// Bridge server\n// --------------------------------------------------------------------------\n\nexport interface BridgeHandle {\n server: Server;\n port: number;\n url: string;\n /** The absolute path of the working file this bridge is serving. */\n workingPath: string;\n /** The page's latest state, for callers that drive the bridge in-process. */\n getEditorState: () => {\n divergent: boolean;\n content: unknown;\n annotations: Annotation[];\n };\n /** Re-read the working file and push it to every connected page. */\n reload: (options?: { consumeAnnotations?: boolean }) => ReloadResult;\n /** With the `fetch` option: the last data-source request's outcome. */\n lastFetch: () => FetchSummary | null;\n close: () => Promise<void>;\n}\n\nexport interface StartBridgeOptions {\n cwd?: string;\n port?: number;\n file?: string;\n harnessFile?: string;\n /** Custom-block mode: serve this definition's specimen instead of a template. */\n customBlock?: string;\n /** Custom-block mode only: a template the specimen is appended to. */\n host?: string;\n /** Environment the data-source recipe resolves `${env:…}` from. Defaults to process.env. */\n env?: Record<string, string | undefined>;\n /**\n * Custom-block mode only: run the `dataSourcePreview` recipe and add a\n * \"fetched\" state, filled from the real endpoint, to the specimen. `values`\n * override the field defaults for the request (the id it fetches by). It\n * runs before the bridge starts listening and again on every `POST\n * /reload`; the in-process `reload()` reuses the last result.\n */\n fetch?: { values: Record<string, unknown> };\n}\n\n/**\n * Start the bridge HTTP server. Pure server + in-memory sync state; callers own\n * process lifecycle. Resolves once listening.\n *\n * The returned handle exposes `getEditorState` and `reload` directly, so an\n * in-process caller (the MCP server) never has to make an HTTP request to its\n * own bridge — that is the whole reason live mode can work inside a sandboxed\n * agent. The equivalent HTTP endpoints stay for the skill's out-of-process CLI.\n */\nexport function startBridge({\n cwd = process.cwd(),\n port = 0,\n file = join(WORKING_DIR, \"template.json\"),\n harnessFile = HARNESS_FILE,\n customBlock,\n host,\n env = process.env,\n fetch: fetchOption,\n}: StartBridgeOptions = {}): Promise<BridgeHandle> {\n const abs = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p));\n const customBlockPath = customBlock ? abs(customBlock) : null;\n const hostPath = host ? abs(host) : null;\n const workingPath = customBlockPath ?? abs(file);\n\n // Sync state. `baseline` is the editor's NORMALIZED view of the caller's last\n // content (captured by the page right after it applies an update), so the\n // editor's own load-time normalization never reads as a user edit.\n // `editorCurrent` is the page's latest getContent(); `divergent` is true when\n // it structurally differs from `baseline` — i.e. the user hand-edited.\n const state: {\n baseline: unknown;\n editorCurrent: unknown;\n divergent: boolean;\n annotations: Annotation[];\n } = {\n baseline: null,\n editorCurrent: null,\n divergent: false,\n annotations: [],\n };\n let nextAnnotationNumber = 1;\n function mintAnnotationId(): string {\n const n = nextAnnotationNumber++;\n return `a${n}-${Date.now().toString(36)}`;\n }\n const clients = new Set<ServerResponse>();\n\n function broadcastTemplate(content: unknown): void {\n const payload = `event: template\\ndata: ${JSON.stringify(content)}\\n\\n`;\n for (const res of clients) res.write(payload);\n }\n\n /** Why the bridge won't run this definition's recipe, or null when it will. */\n function recipeRefusal(def: CustomBlockWorkingFile): string | null {\n if (!def.dataSourcePreview) return \"This block has no dataSourcePreview.\";\n const errors = checkRecipe(def).filter((i) => i.severity === \"error\");\n return errors.length > 0\n ? `The dataSourcePreview recipe is refused: ${errors\n .map((i) => `${i.ruleId}: ${i.message}`)\n .join(\"; \")}`\n : null;\n }\n\n // The fetched state's last outcome, null until the first run.\n let fetched: (FetchedSpecimen & { unmapped?: string[] }) | null = null;\n\n async function refreshFetched(): Promise<void> {\n const data = readWorkingFile(customBlockPath as string);\n if (data === null || !validateCustomBlockDefinition(data).valid) {\n fetched = {\n ok: false,\n error: \"The custom block definition is missing or invalid.\",\n };\n return;\n }\n const def = data as CustomBlockWorkingFile;\n const recipe = def.dataSourcePreview;\n const refusal = recipeRefusal(def);\n if (!recipe || refusal !== null) {\n fetched = { ok: false, error: refusal ?? \"\" };\n return;\n }\n const values = {\n ...buildSpecimen(def)[0].fieldValues,\n ...fetchOption?.values,\n };\n const result = await runRecipe(recipe, values, { env });\n fetched = result.ok\n ? {\n ok: true,\n fieldValues: { ...values, ...result.values },\n unmapped: result.unmapped,\n }\n : { ok: false, error: result.error };\n }\n\n function lastFetch(): FetchSummary | null {\n if (!fetched) return null;\n return fetched.ok\n ? { ok: true, unmapped: fetched.unmapped ?? [] }\n : { ok: false, error: fetched.error };\n }\n\n type BlockView =\n | {\n ok: true;\n def: CustomBlockWorkingFile;\n payload: unknown;\n template: TemplateContent;\n }\n | { ok: false; error: string };\n\n // Read fresh on every request: the agent edits the file between reloads,\n // and the page must never be handed a definition that failed validation.\n function readBlockView(): BlockView {\n const data = readWorkingFile(customBlockPath as string);\n if (data === null || !validateCustomBlockDefinition(data).valid) {\n return {\n ok: false,\n error:\n \"The custom block definition is missing or invalid. Run `templatical custom-block validate` on it.\",\n };\n }\n let hostContent: TemplateContent | undefined;\n if (hostPath) {\n const h = readWorkingFile(hostPath);\n if (h === null || !validateTemplate(h).valid) {\n return {\n ok: false,\n error:\n \"The --host template is missing or invalid. Run `templatical validate` on it.\",\n };\n }\n hostContent = h as TemplateContent;\n }\n const def = data as CustomBlockWorkingFile;\n const { dataSourcePreview, ...definition } = def;\n // The fetched state leads the specimen: it is the realistic one.\n const base = hostContent ?? createDefaultTemplateContent();\n const lead =\n fetchOption && fetched ? fetchedSpecimenBlocks(def, fetched) : [];\n return {\n ok: true,\n def,\n payload: {\n definition,\n dataSource: dataSourcePreview\n ? { label: dataSourcePreview.label }\n : null,\n },\n template: buildSpecimenTemplate(def, {\n ...base,\n blocks: [...base.blocks, ...lead],\n }),\n };\n }\n\n function sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(body));\n }\n\n function getEditorState(): {\n divergent: boolean;\n content: unknown;\n annotations: Annotation[];\n } {\n return {\n divergent: state.divergent,\n content: state.editorCurrent,\n annotations: state.annotations,\n };\n }\n\n function reload(options?: { consumeAnnotations?: boolean }): ReloadResult {\n // A failed read pushes nothing and leaves the page's state alone: the\n // page still shows what it showed, so its divergence and notes still hold.\n if (customBlockPath) {\n const view = readBlockView();\n if (!view.ok) {\n return {\n ok: false,\n clients: clients.size,\n consumed: false,\n mode: \"custom-block\",\n error: view.error,\n };\n }\n state.baseline = null;\n state.editorCurrent = null;\n state.divergent = false;\n const consumed = options?.consumeAnnotations === true;\n if (consumed) state.annotations = [];\n const payload = `event: custom-block\\ndata: ${JSON.stringify({ block: view.payload, template: view.template })}\\n\\n`;\n for (const res of clients) res.write(payload);\n const fetch = fetchOption ? lastFetch() : null;\n return {\n ok: true,\n clients: clients.size,\n consumed,\n mode: \"custom-block\",\n ...(fetch ? { fetch } : {}),\n };\n }\n\n // The freshly-written file is the new baseline, so any pending user edit is\n // superseded. Notes stay: a plain reload is an ordinary edit, and clearing\n // them here would drop a queue the agent has not applied. consumeAnnotations\n // clears only after the file was actually read — clearing first would drop\n // the notes and push nothing when the file is missing.\n const content = readWorkingFile(workingPath);\n if (content === null) {\n return {\n ok: false,\n clients: clients.size,\n consumed: false,\n mode: \"template\",\n error: `${workingPath} is missing or isn't valid JSON. Run \\`templatical validate\\` on it.`,\n };\n }\n state.baseline = null;\n state.editorCurrent = null;\n state.divergent = false;\n const consumed = options?.consumeAnnotations === true;\n if (consumed) state.annotations = [];\n broadcastTemplate(content);\n return { ok: true, clients: clients.size, consumed, mode: \"template\" };\n }\n\n const server = createServer(async (req, res) => {\n const { pathname } = new URL(req.url ?? \"/\", \"http://localhost\");\n const method = req.method ?? \"GET\";\n\n try {\n // Only this listener's own loopback names are served: a DNS-rebound\n // hostname would otherwise let any web page read the bridge as same-origin.\n const addr = server.address();\n const listening =\n typeof addr === \"object\" && addr !== null ? addr.port : port;\n const hostHeader = req.headers.host ?? \"\";\n if (!loopbackNames(listening).some((n) => n === hostHeader)) {\n res\n .writeHead(403, { \"content-type\": \"text/plain\" })\n .end(\"Forbidden host.\");\n return;\n }\n\n // Writes. The Host check above doesn't stop a page on another site from\n // sending a simple cross-origin POST to localhost (text/plain, no\n // preflight): its response is unreadable, but the write still lands, so\n // a note it posts would reach the agent as the user's own. A browser\n // attaches its Origin to every such request, so a foreign one is\n // refused, and a POST must be JSON, which no simple request can be. PUT\n // and DELETE need a preflight cross-site, which the bridge never grants.\n if (method !== \"GET\" && method !== \"HEAD\") {\n const origin = req.headers.origin;\n if (\n origin !== undefined &&\n !loopbackNames(listening).some((n) => origin === `http://${n}`)\n ) {\n sendJson(res, 403, { error: \"Forbidden origin.\" });\n return;\n }\n const type = (req.headers[\"content-type\"] ?? \"\").toLowerCase();\n if (method === \"POST\" && !type.startsWith(\"application/json\")) {\n sendJson(res, 415, { error: \"Send application/json.\" });\n return;\n }\n }\n\n if (\n method === \"GET\" &&\n (pathname === \"/\" || pathname === \"/index.html\")\n ) {\n const html = readFileSync(harnessFile, \"utf8\").replaceAll(\n \"{{EDITOR_VERSION}}\",\n EDITOR_VERSION,\n );\n res.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(html);\n return;\n }\n\n if (method === \"GET\" && pathname === \"/custom-block\") {\n if (!customBlockPath) {\n res.writeHead(204).end();\n return;\n }\n const view = readBlockView();\n if (view.ok) sendJson(res, 200, view.payload);\n else sendJson(res, 422, { error: view.error });\n return;\n }\n\n if (method === \"POST\" && pathname === \"/data-source/fetch\") {\n if (!customBlockPath) {\n res.writeHead(404, { \"content-type\": \"text/plain\" }).end(\"Not found\");\n return;\n }\n const view = readBlockView();\n if (!view.ok) {\n sendJson(res, 422, { error: view.error });\n return;\n }\n // The recipe gate runs before the page's request is even read: a\n // recipe `custom-block validate` rejects is never sent anywhere.\n const recipe = view.def.dataSourcePreview;\n const refusal = recipeRefusal(view.def);\n if (!recipe || refusal !== null) {\n sendJson(res, recipe ? 422 : 409, { error: refusal });\n return;\n }\n // Only fieldValues is read: the recipe (URL, headers) never comes from the page.\n const body = await readJsonBody(req);\n const fieldValues =\n body?.fieldValues &&\n typeof body.fieldValues === \"object\" &&\n !Array.isArray(body.fieldValues)\n ? (body.fieldValues as Record<string, unknown>)\n : {};\n const result = await runRecipe(recipe, fieldValues, { env });\n if (result.ok) {\n sendJson(res, 200, {\n values: result.values,\n unmapped: result.unmapped,\n });\n } else {\n sendJson(res, 502, { error: result.error });\n }\n return;\n }\n\n if (method === \"GET\" && pathname === \"/template\") {\n if (customBlockPath) {\n const view = readBlockView();\n if (view.ok) sendJson(res, 200, view.template);\n else sendJson(res, 422, { error: view.error });\n return;\n }\n const content = readWorkingFile(workingPath);\n if (content === null) {\n res.writeHead(204).end(); // no working file yet — page inits empty\n return;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(content));\n return;\n }\n\n if (method === \"GET\" && pathname === \"/events\") {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n res.write(\"event: ready\\ndata: {}\\n\\n\");\n clients.add(res);\n const ping = setInterval(() => res.write(\": ping\\n\\n\"), 15000);\n req.on(\"close\", () => {\n clearInterval(ping);\n clients.delete(res);\n });\n return;\n }\n\n if (method === \"POST\" && pathname === \"/content\") {\n const body = await readJsonBody(req);\n const content = body?.content;\n if (body?.baseline) {\n // Post-apply snapshot: this is the editor's normalized view of the\n // caller's content, not a user edit.\n state.baseline = content ?? null;\n state.editorCurrent = content ?? null;\n state.divergent = false;\n } else {\n state.editorCurrent = content ?? null;\n state.divergent =\n state.baseline !== null && !deepEqual(content, state.baseline);\n }\n res.writeHead(204).end();\n return;\n }\n\n if (method === \"GET\" && pathname === \"/content\") {\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(getEditorState()));\n return;\n }\n\n if (method === \"POST\" && pathname === \"/annotations\") {\n const body = await readJsonBody(req);\n const parsed = readNoteText(body?.text);\n if (\"error\" in parsed) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(parsed.error);\n return;\n }\n if (state.annotations.length >= NOTE_QUEUE_MAX) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"The note queue is full (40).\");\n return;\n }\n const blockId = readOptionalId(body?.blockId);\n let parentBlockId = readOptionalId(body?.parentBlockId);\n if (parentBlockId === blockId) parentBlockId = null;\n const annotation: Annotation = {\n id: mintAnnotationId(),\n blockId,\n parentBlockId,\n blockType: readBlockType(body?.blockType),\n label: readLabel(body?.label),\n text: parsed.text,\n createdAt: Date.now(),\n };\n state.annotations.push(annotation);\n res.writeHead(201, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(annotation));\n return;\n }\n\n const noteId = annotationIdFromPath(pathname);\n if (noteId && (method === \"PUT\" || method === \"DELETE\")) {\n const index = state.annotations.findIndex((note) => note.id === noteId);\n if (index < 0) {\n noteNotFound(res);\n return;\n }\n if (method === \"DELETE\") {\n state.annotations.splice(index, 1);\n res.writeHead(204).end();\n return;\n }\n const body = await readJsonBody(req);\n const parsed = readNoteText(body?.text);\n if (\"error\" in parsed) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(parsed.error);\n return;\n }\n const updated: Annotation = {\n ...state.annotations[index],\n text: parsed.text,\n };\n state.annotations[index] = updated;\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(updated));\n return;\n }\n\n if (method === \"POST\" && pathname === \"/reload\") {\n const body = await readJsonBody(req);\n if (fetchOption && customBlockPath) await refreshFetched();\n const result = reload({\n consumeAnnotations: body?.consumeAnnotations === true,\n });\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(result));\n return;\n }\n\n res.writeHead(404, { \"content-type\": \"text/plain\" }).end(\"Not found\");\n } catch (err) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(`Bad request: ${(err as Error).message}`);\n }\n });\n\n // The first fetch lands before the page can load, so it never shows a\n // sheet without the fetched state.\n const ready =\n fetchOption && customBlockPath ? refreshFetched() : Promise.resolve();\n\n return ready.then(\n () =>\n new Promise<BridgeHandle>((resolvePromise, rejectPromise) => {\n // listen() reports failures (e.g. EADDRINUSE) as an 'error' event, not a\n // throw — without this the error would be unhandled and crash the process.\n const onListenError = (err: Error) => rejectPromise(err);\n server.once(\"error\", onListenError);\n server.listen(port, \"127.0.0.1\", () => {\n server.removeListener(\"error\", onListenError);\n const address = server.address();\n const actualPort =\n typeof address === \"object\" && address !== null\n ? address.port\n : port;\n resolvePromise({\n server,\n port: actualPort,\n url: `http://localhost:${actualPort}/`,\n workingPath,\n getEditorState,\n reload,\n lastFetch,\n close: () =>\n new Promise<void>((r) => {\n for (const c of clients) c.end();\n clients.clear();\n server.close(() => r());\n }),\n });\n });\n }),\n );\n}\n\nexport interface PreferringHandle extends BridgeHandle {\n fellBack: boolean;\n preferredPort?: number;\n}\n\n/**\n * Start the bridge on `preferredPort`, falling back to an OS-assigned free port\n * when that one is occupied — so a busy port never fails the launch. The page\n * uses relative URLs, so the actual port is discovered, not assumed. The\n * returned handle carries `fellBack: true` when it landed on a different port.\n */\nexport async function startBridgePreferring({\n preferredPort = DEFAULT_PORT,\n ...opts\n}: StartBridgeOptions & {\n preferredPort?: number;\n} = {}): Promise<PreferringHandle> {\n try {\n const handle = await startBridge({ ...opts, port: preferredPort });\n return { ...handle, fellBack: false };\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code !== \"EADDRINUSE\") throw err;\n const handle = await startBridge({ ...opts, port: 0 });\n return { ...handle, fellBack: true, preferredPort };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/live/index.ts"],"sourcesContent":["// The local live-preview bridge.\n//\n// It serves live/index.html (which loads the REAL Templatical editor from the\n// CDN), streams agent-driven template updates to the page over Server-Sent\n// Events, and buffers the page's hand-edits so the caller can detect divergence\n// (a user edit in the browser) before overwriting.\n//\n// This package's own `live` CLI command drives it today, over localhost HTTP.\n// It stays side-effect free at import time and writes nothing to stdout so\n// that a future stdio-based caller — an MCP server reserving stdout for\n// JSON-RPC — can be added without auditing this module first: a stray write\n// here would corrupt that protocol. Keep all human-facing output in the\n// callers (cli/output.ts is the CLI's own single writer;\n// tests/cli-output-discipline.test.ts enforces that no other module under\n// src/ touches stdout).\n\nimport { createServer, type Server, type ServerResponse } from \"node:http\";\nimport { spawn } from \"node:child_process\";\nimport { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport {\n createDefaultTemplateContent,\n type TemplateContent,\n} from \"@templatical/types\";\nimport {\n buildSpecimen,\n buildSpecimenTemplate,\n checkRecipe,\n runRecipe,\n validateCustomBlockDefinition,\n type CustomBlockWorkingFile,\n} from \"../custom-block\";\nimport {\n fetchedSpecimenBlocks,\n type FetchedSpecimen,\n} from \"../custom-block/specimen\";\nimport { validateTemplate } from \"../validate\";\n\nconst here = dirname(fileURLToPath(import.meta.url));\n\n// The CDN editor version the live harness loads. It is PINNED to this package's\n// schema version: schema.json is generated from @templatical/types, and types +\n// editor bump in lockstep (changesets fixed group), so the editor at this\n// version has the same block model the schema describes.\n//\n// Kept in step automatically by scripts/sync-pins.mjs at release time.\n// tests/cdn-pin.test.ts is the safety net that fails CI if it ever drifts.\nexport const EDITOR_VERSION = \"0.48.0\";\n\nexport const DEFAULT_PORT = 4747;\nexport const WORKING_DIR = \".templatical\";\nexport const PID_FILE = join(WORKING_DIR, \"live-server.pid\");\n\n// Resolved from this module rather than the cwd so it works identically from\n// src/live/ (tests) and dist/live/ (published) — both sit two levels under the\n// package root, where live/index.html ships.\nconst HARNESS_FILE = resolve(here, \"../../live/index.html\");\n\n// --------------------------------------------------------------------------\n// Pure helpers\n// --------------------------------------------------------------------------\n\n/** Order-insensitive structural equality for parsed-JSON template content. */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\") return false;\n const aArr = Array.isArray(a);\n if (aArr !== Array.isArray(b)) return false;\n if (aArr) {\n const aList = a as unknown[];\n const bList = b as unknown[];\n if (aList.length !== bList.length) return false;\n for (let i = 0; i < aList.length; i++) {\n if (!deepEqual(aList[i], bList[i])) return false;\n }\n return true;\n }\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const ak = Object.keys(aObj);\n const bk = Object.keys(bObj);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!Object.prototype.hasOwnProperty.call(bObj, k)) return false;\n if (!deepEqual(aObj[k], bObj[k])) return false;\n }\n return true;\n}\n\n/** Read + parse the working file, or return null when it is absent/unparseable. */\nexport function readWorkingFile(path: string): unknown {\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf8\"));\n } catch {\n return null;\n }\n}\n\n/** List the working templates in a project's `.templatical/` folder, by name. */\nexport function listWorkingFiles(cwd: string): string[] {\n const dir = resolve(cwd, WORKING_DIR);\n if (!existsSync(dir)) return [];\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".json\"))\n .sort();\n}\n\nasync function readJsonBody(req: NodeJS.ReadableStream): Promise<\n | {\n content?: unknown;\n baseline?: boolean;\n blockId?: string;\n parentBlockId?: unknown;\n blockType?: unknown;\n label?: unknown;\n text?: string;\n consumeAnnotations?: boolean;\n fieldValues?: unknown;\n }\n | undefined\n> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n const raw = Buffer.concat(chunks).toString(\"utf8\");\n return raw ? JSON.parse(raw) : undefined;\n}\n\n/**\n * Open a URL in the user's default browser — best-effort and cross-platform.\n * A silent no-op where there's no browser (headless / CI / sandbox); callers\n * always surface the URL too, so nothing is lost when this does nothing.\n */\nexport function openBrowser(url: string): void {\n const [cmd, cmdArgs]: [string, string[]] =\n process.platform === \"darwin\"\n ? [\"open\", [url]]\n : process.platform === \"win32\"\n ? [\"cmd\", [\"/c\", \"start\", \"\", url]]\n : [\"xdg-open\", [url]];\n try {\n const child = spawn(cmd, cmdArgs, { stdio: \"ignore\", detached: true });\n child.on(\"error\", () => {}); // no browser available — ignore\n child.unref();\n } catch {\n /* ignore — the URL is surfaced by the caller regardless */\n }\n}\n\n// --------------------------------------------------------------------------\n// Pidfile helpers (used by this package's own `live` CLI command; a future\n// stdio-based MCP server would own its process lifecycle directly and would\n// not need them)\n// --------------------------------------------------------------------------\n\nexport type LiveMode = \"template\" | \"custom-block\";\n\nexport interface PidfileInfo {\n pid: number;\n port: number;\n /** Absent in a pidfile from a template-only bridge; read as \"template\". */\n mode?: LiveMode;\n /** The absolute path the server is serving. */\n path?: string;\n /** Custom-block mode: the absolute --host template, when one was given. */\n host?: string;\n /** Custom-block mode with --fetch: the --values the recipe runs with. */\n fetch?: Record<string, unknown>;\n}\n\n/** How the data-source request for the fetched specimen state went. */\nexport type FetchSummary =\n { ok: true; unmapped: string[] } | { ok: false; error: string };\n\n/** What a reload pushed, and why it pushed nothing when `ok` is false. */\nexport interface ReloadResult {\n ok: boolean;\n clients: number;\n consumed: boolean;\n mode: LiveMode;\n error?: string;\n /** With the `fetch` option: the request behind the fetched state. */\n fetch?: FetchSummary;\n}\n\nexport function pidfilePath(cwd: string): string {\n return resolve(cwd, PID_FILE);\n}\n\nexport function readPidfile(cwd: string): PidfileInfo | null {\n const p = pidfilePath(cwd);\n if (!existsSync(p)) return null;\n try {\n return JSON.parse(readFileSync(p, \"utf8\")) as PidfileInfo;\n } catch {\n return null;\n }\n}\n\nexport function processAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * A note the user left in the browser, against a block or the template as a\n * whole. Delivered on GET /content - the call the agent already makes before\n * every change - so notes need no polling and no separate endpoint to drain.\n */\nexport interface Annotation {\n id: string;\n /** null for a note about the template rather than one block. */\n blockId: string | null;\n /** The next data-block-id above the target. null at the top level and for a template note. */\n parentBlockId: string | null;\n blockType: string | null;\n /** Snapshot of the block's text at save time, already truncated. */\n label: string;\n text: string;\n createdAt: number;\n}\n\nconst NOTE_TEXT_MAX = 2000;\nconst NOTE_LABEL_MAX = 80;\nconst NOTE_BLOCK_TYPE_MAX = 40;\nconst NOTE_QUEUE_MAX = 40;\n\nfunction readNoteText(value: unknown): { text: string } | { error: string } {\n const text = typeof value === \"string\" ? value.trim() : \"\";\n if (!text) return { error: \"An annotation needs non-empty `text`.\" };\n if (text.length > NOTE_TEXT_MAX) {\n return { error: \"A note can be at most 2000 characters.\" };\n }\n return { text };\n}\n\nfunction readOptionalId(value: unknown): string | null {\n return typeof value === \"string\" ? value : null;\n}\n\nfunction readBlockType(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n return value.trim().slice(0, NOTE_BLOCK_TYPE_MAX);\n}\n\nfunction readLabel(value: unknown): string {\n if (typeof value !== \"string\") return \"\";\n return value.replace(/\\s+/g, \" \").trim().slice(0, NOTE_LABEL_MAX);\n}\n\nfunction annotationIdFromPath(pathname: string): string | null {\n const prefix = \"/annotations/\";\n if (!pathname.startsWith(prefix)) return null;\n const raw = pathname.slice(prefix.length);\n if (!raw || raw.includes(\"/\")) return null;\n try {\n return decodeURIComponent(raw);\n } catch {\n return null;\n }\n}\n\nfunction loopbackNames(port: number): string[] {\n return [`localhost:${port}`, `127.0.0.1:${port}`, `[::1]:${port}`];\n}\n\nfunction noteNotFound(res: ServerResponse): void {\n res.writeHead(404, { \"content-type\": \"text/plain\" });\n res.end(\"No note with that id.\");\n}\n\n// --------------------------------------------------------------------------\n// Bridge server\n// --------------------------------------------------------------------------\n\nexport interface BridgeHandle {\n server: Server;\n port: number;\n url: string;\n /** The absolute path of the working file this bridge is serving. */\n workingPath: string;\n /** The page's latest state, for callers that drive the bridge in-process. */\n getEditorState: () => {\n divergent: boolean;\n content: unknown;\n annotations: Annotation[];\n };\n /** Re-read the working file and push it to every connected page. */\n reload: (options?: { consumeAnnotations?: boolean }) => ReloadResult;\n /** With the `fetch` option: the last data-source request's outcome. */\n lastFetch: () => FetchSummary | null;\n close: () => Promise<void>;\n}\n\nexport interface StartBridgeOptions {\n cwd?: string;\n port?: number;\n file?: string;\n harnessFile?: string;\n /** Custom-block mode: serve this definition's specimen instead of a template. */\n customBlock?: string;\n /** Custom-block mode only: a template the specimen is appended to. */\n host?: string;\n /** Environment the data-source recipe resolves `${env:…}` from. Defaults to process.env. */\n env?: Record<string, string | undefined>;\n /**\n * Custom-block mode only: run the `dataSourcePreview` recipe and add a\n * \"fetched\" state, filled from the real endpoint, to the specimen. `values`\n * override the field defaults for the request (the id it fetches by). It\n * runs before the bridge starts listening and again on every `POST\n * /reload`; the in-process `reload()` reuses the last result.\n */\n fetch?: { values: Record<string, unknown> };\n}\n\n/**\n * Start the bridge HTTP server. Pure server + in-memory sync state; callers own\n * process lifecycle. Resolves once listening.\n *\n * The returned handle exposes `getEditorState` and `reload` directly, so an\n * in-process caller (the MCP server) never has to make an HTTP request to its\n * own bridge — that is the whole reason live mode can work inside a sandboxed\n * agent. The equivalent HTTP endpoints stay for the skill's out-of-process CLI.\n */\nexport function startBridge({\n cwd = process.cwd(),\n port = 0,\n file = join(WORKING_DIR, \"template.json\"),\n harnessFile = HARNESS_FILE,\n customBlock,\n host,\n env = process.env,\n fetch: fetchOption,\n}: StartBridgeOptions = {}): Promise<BridgeHandle> {\n const abs = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p));\n const customBlockPath = customBlock ? abs(customBlock) : null;\n const hostPath = host ? abs(host) : null;\n const workingPath = customBlockPath ?? abs(file);\n\n // Sync state. `baseline` is the editor's NORMALIZED view of the caller's last\n // content (captured by the page right after it applies an update), so the\n // editor's own load-time normalization never reads as a user edit.\n // `editorCurrent` is the page's latest getContent(); `divergent` is true when\n // it structurally differs from `baseline` — i.e. the user hand-edited.\n const state: {\n baseline: unknown;\n editorCurrent: unknown;\n divergent: boolean;\n annotations: Annotation[];\n } = {\n baseline: null,\n editorCurrent: null,\n divergent: false,\n annotations: [],\n };\n let nextAnnotationNumber = 1;\n function mintAnnotationId(): string {\n const n = nextAnnotationNumber++;\n return `a${n}-${Date.now().toString(36)}`;\n }\n const clients = new Set<ServerResponse>();\n\n function broadcastTemplate(content: unknown): void {\n const payload = `event: template\\ndata: ${JSON.stringify(content)}\\n\\n`;\n for (const res of clients) res.write(payload);\n }\n\n /** Why the bridge won't run this definition's recipe, or null when it will. */\n function recipeRefusal(def: CustomBlockWorkingFile): string | null {\n if (!def.dataSourcePreview) return \"This block has no dataSourcePreview.\";\n const errors = checkRecipe(def).filter((i) => i.severity === \"error\");\n return errors.length > 0\n ? `The dataSourcePreview recipe is refused: ${errors\n .map((i) => `${i.ruleId}: ${i.message}`)\n .join(\"; \")}`\n : null;\n }\n\n // The fetched state's last outcome, null until the first run.\n let fetched: (FetchedSpecimen & { unmapped?: string[] }) | null = null;\n\n async function refreshFetched(): Promise<void> {\n const data = readWorkingFile(customBlockPath as string);\n if (data === null || !validateCustomBlockDefinition(data).valid) {\n fetched = {\n ok: false,\n error: \"The custom block definition is missing or invalid.\",\n };\n return;\n }\n const def = data as CustomBlockWorkingFile;\n const recipe = def.dataSourcePreview;\n const refusal = recipeRefusal(def);\n if (!recipe || refusal !== null) {\n fetched = { ok: false, error: refusal ?? \"\" };\n return;\n }\n const values = {\n ...buildSpecimen(def)[0].fieldValues,\n ...fetchOption?.values,\n };\n const result = await runRecipe(recipe, values, { env });\n fetched = result.ok\n ? {\n ok: true,\n fieldValues: { ...values, ...result.values },\n unmapped: result.unmapped,\n }\n : { ok: false, error: result.error };\n }\n\n function lastFetch(): FetchSummary | null {\n if (!fetched) return null;\n return fetched.ok\n ? { ok: true, unmapped: fetched.unmapped ?? [] }\n : { ok: false, error: fetched.error };\n }\n\n type BlockView =\n | {\n ok: true;\n def: CustomBlockWorkingFile;\n payload: unknown;\n template: TemplateContent;\n }\n | { ok: false; error: string };\n\n // Read fresh on every request: the agent edits the file between reloads,\n // and the page must never be handed a definition that failed validation.\n function readBlockView(): BlockView {\n const data = readWorkingFile(customBlockPath as string);\n if (data === null || !validateCustomBlockDefinition(data).valid) {\n return {\n ok: false,\n error:\n \"The custom block definition is missing or invalid. Run `templatical custom-block validate` on it.\",\n };\n }\n let hostContent: TemplateContent | undefined;\n if (hostPath) {\n const h = readWorkingFile(hostPath);\n if (h === null || !validateTemplate(h).valid) {\n return {\n ok: false,\n error:\n \"The --host template is missing or invalid. Run `templatical validate` on it.\",\n };\n }\n hostContent = h as TemplateContent;\n }\n const def = data as CustomBlockWorkingFile;\n const { dataSourcePreview, ...definition } = def;\n // The fetched state leads the specimen: it is the realistic one.\n const base = hostContent ?? createDefaultTemplateContent();\n const lead =\n fetchOption && fetched ? fetchedSpecimenBlocks(def, fetched) : [];\n return {\n ok: true,\n def,\n payload: {\n definition,\n dataSource: dataSourcePreview\n ? { label: dataSourcePreview.label }\n : null,\n },\n template: buildSpecimenTemplate(def, {\n ...base,\n blocks: [...base.blocks, ...lead],\n }),\n };\n }\n\n function sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(body));\n }\n\n function getEditorState(): {\n divergent: boolean;\n content: unknown;\n annotations: Annotation[];\n } {\n return {\n divergent: state.divergent,\n content: state.editorCurrent,\n annotations: state.annotations,\n };\n }\n\n function reload(options?: { consumeAnnotations?: boolean }): ReloadResult {\n // A failed read pushes nothing and leaves the page's state alone: the\n // page still shows what it showed, so its divergence and notes still hold.\n if (customBlockPath) {\n const view = readBlockView();\n if (!view.ok) {\n return {\n ok: false,\n clients: clients.size,\n consumed: false,\n mode: \"custom-block\",\n error: view.error,\n };\n }\n state.baseline = null;\n state.editorCurrent = null;\n state.divergent = false;\n const consumed = options?.consumeAnnotations === true;\n if (consumed) state.annotations = [];\n const payload = `event: custom-block\\ndata: ${JSON.stringify({ block: view.payload, template: view.template })}\\n\\n`;\n for (const res of clients) res.write(payload);\n const fetch = fetchOption ? lastFetch() : null;\n return {\n ok: true,\n clients: clients.size,\n consumed,\n mode: \"custom-block\",\n ...(fetch ? { fetch } : {}),\n };\n }\n\n // The freshly-written file is the new baseline, so any pending user edit is\n // superseded. Notes stay: a plain reload is an ordinary edit, and clearing\n // them here would drop a queue the agent has not applied. consumeAnnotations\n // clears only after the file was actually read — clearing first would drop\n // the notes and push nothing when the file is missing.\n const content = readWorkingFile(workingPath);\n if (content === null) {\n return {\n ok: false,\n clients: clients.size,\n consumed: false,\n mode: \"template\",\n error: `${workingPath} is missing or isn't valid JSON. Run \\`templatical validate\\` on it.`,\n };\n }\n state.baseline = null;\n state.editorCurrent = null;\n state.divergent = false;\n const consumed = options?.consumeAnnotations === true;\n if (consumed) state.annotations = [];\n broadcastTemplate(content);\n return { ok: true, clients: clients.size, consumed, mode: \"template\" };\n }\n\n const server = createServer(async (req, res) => {\n const { pathname } = new URL(req.url ?? \"/\", \"http://localhost\");\n const method = req.method ?? \"GET\";\n\n try {\n // Only this listener's own loopback names are served: a DNS-rebound\n // hostname would otherwise let any web page read the bridge as same-origin.\n const addr = server.address();\n const listening =\n typeof addr === \"object\" && addr !== null ? addr.port : port;\n const hostHeader = req.headers.host ?? \"\";\n if (!loopbackNames(listening).some((n) => n === hostHeader)) {\n res\n .writeHead(403, { \"content-type\": \"text/plain\" })\n .end(\"Forbidden host.\");\n return;\n }\n\n // Writes. The Host check above doesn't stop a page on another site from\n // sending a simple cross-origin POST to localhost (text/plain, no\n // preflight): its response is unreadable, but the write still lands, so\n // a note it posts would reach the agent as the user's own. A browser\n // attaches its Origin to every such request, so a foreign one is\n // refused, and a POST must be JSON, which no simple request can be. PUT\n // and DELETE need a preflight cross-site, which the bridge never grants.\n if (method !== \"GET\" && method !== \"HEAD\") {\n const origin = req.headers.origin;\n if (\n origin !== undefined &&\n !loopbackNames(listening).some((n) => origin === `http://${n}`)\n ) {\n sendJson(res, 403, { error: \"Forbidden origin.\" });\n return;\n }\n const type = (req.headers[\"content-type\"] ?? \"\").toLowerCase();\n if (method === \"POST\" && !type.startsWith(\"application/json\")) {\n sendJson(res, 415, { error: \"Send application/json.\" });\n return;\n }\n }\n\n if (\n method === \"GET\" &&\n (pathname === \"/\" || pathname === \"/index.html\")\n ) {\n const html = readFileSync(harnessFile, \"utf8\").replaceAll(\n \"{{EDITOR_VERSION}}\",\n EDITOR_VERSION,\n );\n res.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n res.end(html);\n return;\n }\n\n if (method === \"GET\" && pathname === \"/custom-block\") {\n if (!customBlockPath) {\n res.writeHead(204).end();\n return;\n }\n const view = readBlockView();\n if (view.ok) sendJson(res, 200, view.payload);\n else sendJson(res, 422, { error: view.error });\n return;\n }\n\n if (method === \"POST\" && pathname === \"/data-source/fetch\") {\n if (!customBlockPath) {\n res.writeHead(404, { \"content-type\": \"text/plain\" }).end(\"Not found\");\n return;\n }\n const view = readBlockView();\n if (!view.ok) {\n sendJson(res, 422, { error: view.error });\n return;\n }\n // The recipe gate runs before the page's request is even read: a\n // recipe `custom-block validate` rejects is never sent anywhere.\n const recipe = view.def.dataSourcePreview;\n const refusal = recipeRefusal(view.def);\n if (!recipe || refusal !== null) {\n sendJson(res, recipe ? 422 : 409, { error: refusal });\n return;\n }\n // Only fieldValues is read: the recipe (URL, headers) never comes from the page.\n const body = await readJsonBody(req);\n const fieldValues =\n body?.fieldValues &&\n typeof body.fieldValues === \"object\" &&\n !Array.isArray(body.fieldValues)\n ? (body.fieldValues as Record<string, unknown>)\n : {};\n const result = await runRecipe(recipe, fieldValues, { env });\n if (result.ok) {\n sendJson(res, 200, {\n values: result.values,\n unmapped: result.unmapped,\n });\n } else {\n sendJson(res, 502, { error: result.error });\n }\n return;\n }\n\n if (method === \"GET\" && pathname === \"/template\") {\n if (customBlockPath) {\n const view = readBlockView();\n if (view.ok) sendJson(res, 200, view.template);\n else sendJson(res, 422, { error: view.error });\n return;\n }\n const content = readWorkingFile(workingPath);\n if (content === null) {\n res.writeHead(204).end(); // no working file yet — page inits empty\n return;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(content));\n return;\n }\n\n if (method === \"GET\" && pathname === \"/events\") {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n res.write(\"event: ready\\ndata: {}\\n\\n\");\n clients.add(res);\n const ping = setInterval(() => res.write(\": ping\\n\\n\"), 15000);\n req.on(\"close\", () => {\n clearInterval(ping);\n clients.delete(res);\n });\n return;\n }\n\n if (method === \"POST\" && pathname === \"/content\") {\n const body = await readJsonBody(req);\n const content = body?.content;\n if (body?.baseline) {\n // Post-apply snapshot: this is the editor's normalized view of the\n // caller's content, not a user edit.\n state.baseline = content ?? null;\n state.editorCurrent = content ?? null;\n state.divergent = false;\n } else {\n state.editorCurrent = content ?? null;\n state.divergent =\n state.baseline !== null && !deepEqual(content, state.baseline);\n }\n res.writeHead(204).end();\n return;\n }\n\n if (method === \"GET\" && pathname === \"/content\") {\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(getEditorState()));\n return;\n }\n\n if (method === \"POST\" && pathname === \"/annotations\") {\n const body = await readJsonBody(req);\n const parsed = readNoteText(body?.text);\n if (\"error\" in parsed) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(parsed.error);\n return;\n }\n if (state.annotations.length >= NOTE_QUEUE_MAX) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(\"The note queue is full (40).\");\n return;\n }\n const blockId = readOptionalId(body?.blockId);\n let parentBlockId = readOptionalId(body?.parentBlockId);\n if (parentBlockId === blockId) parentBlockId = null;\n const annotation: Annotation = {\n id: mintAnnotationId(),\n blockId,\n parentBlockId,\n blockType: readBlockType(body?.blockType),\n label: readLabel(body?.label),\n text: parsed.text,\n createdAt: Date.now(),\n };\n state.annotations.push(annotation);\n res.writeHead(201, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(annotation));\n return;\n }\n\n const noteId = annotationIdFromPath(pathname);\n if (noteId && (method === \"PUT\" || method === \"DELETE\")) {\n const index = state.annotations.findIndex((note) => note.id === noteId);\n if (index < 0) {\n noteNotFound(res);\n return;\n }\n if (method === \"DELETE\") {\n state.annotations.splice(index, 1);\n res.writeHead(204).end();\n return;\n }\n const body = await readJsonBody(req);\n const parsed = readNoteText(body?.text);\n if (\"error\" in parsed) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(parsed.error);\n return;\n }\n const updated: Annotation = {\n ...state.annotations[index],\n text: parsed.text,\n };\n state.annotations[index] = updated;\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(updated));\n return;\n }\n\n if (method === \"POST\" && pathname === \"/reload\") {\n const body = await readJsonBody(req);\n if (fetchOption && customBlockPath) await refreshFetched();\n const result = reload({\n consumeAnnotations: body?.consumeAnnotations === true,\n });\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(result));\n return;\n }\n\n res.writeHead(404, { \"content-type\": \"text/plain\" }).end(\"Not found\");\n } catch (err) {\n res.writeHead(400, { \"content-type\": \"text/plain\" });\n res.end(`Bad request: ${(err as Error).message}`);\n }\n });\n\n // The first fetch lands before the page can load, so it never shows a\n // sheet without the fetched state.\n const ready =\n fetchOption && customBlockPath ? refreshFetched() : Promise.resolve();\n\n return ready.then(\n () =>\n new Promise<BridgeHandle>((resolvePromise, rejectPromise) => {\n // listen() reports failures (e.g. EADDRINUSE) as an 'error' event, not a\n // throw — without this the error would be unhandled and crash the process.\n const onListenError = (err: Error) => rejectPromise(err);\n server.once(\"error\", onListenError);\n server.listen(port, \"127.0.0.1\", () => {\n server.removeListener(\"error\", onListenError);\n const address = server.address();\n const actualPort =\n typeof address === \"object\" && address !== null\n ? address.port\n : port;\n resolvePromise({\n server,\n port: actualPort,\n url: `http://localhost:${actualPort}/`,\n workingPath,\n getEditorState,\n reload,\n lastFetch,\n close: () =>\n new Promise<void>((r) => {\n for (const c of clients) c.end();\n clients.clear();\n server.close(() => r());\n }),\n });\n });\n }),\n );\n}\n\nexport interface PreferringHandle extends BridgeHandle {\n fellBack: boolean;\n preferredPort?: number;\n}\n\n/**\n * Start the bridge on `preferredPort`, falling back to an OS-assigned free port\n * when that one is occupied — so a busy port never fails the launch. The page\n * uses relative URLs, so the actual port is discovered, not assumed. The\n * returned handle carries `fellBack: true` when it landed on a different port.\n */\nexport async function startBridgePreferring({\n preferredPort = DEFAULT_PORT,\n ...opts\n}: StartBridgeOptions & {\n preferredPort?: number;\n} = {}): Promise<PreferringHandle> {\n try {\n const handle = await startBridge({ ...opts, port: preferredPort });\n return { ...handle, fellBack: false };\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code !== \"EADDRINUSE\") throw err;\n const handle = await startBridge({ ...opts, port: 0 });\n return { ...handle, fellBack: true, preferredPort };\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import "./custom-block-sAM61QPU.js";
1
+ import "./custom-block-CsZN9qMM.js";
2
2
  import { safeClone } from "@templatical/types";
3
3
  //#region src/operations.ts
4
4
  /** Columns a layout declares. Mirrors the helper in core's editor. */
@@ -169,4 +169,4 @@ function applyOperation(content, payload) {
169
169
  //#endregion
170
170
  export { getColumnCount as n, applyOperation as t };
171
171
 
172
- //# sourceMappingURL=src-Ce1Oh8m_.js.map
172
+ //# sourceMappingURL=src-SZZkttBC.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"src-Ce1Oh8m_.js","names":[],"sources":["../src/operations.ts"],"sourcesContent":["// A pure reducer over the template operation vocabulary.\n//\n// `applyOperation(content, payload) -> { ok, content, error }`. The input is\n// never mutated: every operation works on a `safeClone` and returns a new\n// document, so a rejected operation leaves the caller's copy provably untouched.\n//\n// Two deliberate differences from the editor's own mutators in\n// `@templatical/core` (`src/editor.ts`), which this mirrors:\n//\n// 1. **Failure is explicit.** Core silently `return`s on an invalid operation,\n// which is right for a UI — a drag that can't drop simply doesn't. An agent\n// gets no such feedback, so a silent no-op would read as success and the\n// next instruction would build on a state that never happened. Every\n// rejection here carries a reason.\n// 2. **No collaboration locks.** `lockedBlocks` is a Cloud concern; this runs\n// against a local working file with a single writer.\n//\n// Core is FSL and cannot be imported here, so the shared invariants — above all\n// the section-into-column refusal (issue #292) — are reimplemented. That is a\n// real drift risk: a guard added to core will not appear here. `tests/\n// operations.test.ts` states each invariant against both descriptions so the\n// pair has to be reconciled deliberately.\n//\n// Note the payload keys are camelCase (`blockId`, `targetSectionId`), matching\n// `@templatical/types` and core. Cloud's MCP tools emit snake_case in their own\n// `data` payloads, so this reducer is NOT wire-compatible with Cloud broadcasts\n// and is not meant to be.\n\nimport { safeClone } from \"@templatical/types\";\nimport type {\n Block,\n ColumnLayout,\n TemplateOperationPayload,\n SectionBlock,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\n\n/** Columns a layout declares. Mirrors the helper in core's editor. */\nexport function getColumnCount(layout: ColumnLayout): number {\n if (layout === \"1\") return 1;\n if (layout === \"3\") return 3;\n return 2;\n}\n\nfunction findBlockById(blocks: Block[], id: string): Block | null {\n for (const block of blocks) {\n if (block.id === id) return block;\n if (block.type === \"section\") {\n for (const column of block.children) {\n const found = findBlockById(column, id);\n if (found) return found;\n }\n }\n }\n return null;\n}\n\ninterface ParentRef {\n blocks: Block[];\n sectionId?: string;\n columnIndex?: number;\n}\n\nfunction findBlockParent(\n blocks: Block[],\n id: string,\n parent: ParentRef = { blocks },\n): ParentRef | null {\n for (const block of blocks) {\n if (block.id === id) return parent;\n if (block.type === \"section\") {\n for (let colIdx = 0; colIdx < block.children.length; colIdx++) {\n const result = findBlockParent(block.children[colIdx], id, {\n blocks: block.children[colIdx],\n sectionId: block.id,\n columnIndex: colIdx,\n });\n if (result) return result;\n }\n }\n }\n return null;\n}\n\nexport interface OperationResult {\n ok: boolean;\n /** The new document on success; the unchanged input on failure. */\n content: TemplateContent;\n error?: string;\n}\n\nfunction reject(content: TemplateContent, error: string): OperationResult {\n return { ok: false, content, error };\n}\n\n/**\n * Resolve the array an operation targets, enforcing the column rules.\n * Returns a string on rejection so callers can surface the reason.\n */\nfunction resolveTarget(\n content: TemplateContent,\n targetSectionId: string | undefined,\n columnIndex: number,\n): Block[] | string {\n if (!targetSectionId) return content.blocks;\n\n const section = findBlockById(content.blocks, targetSectionId);\n if (!section) return `No block with id \"${targetSectionId}\".`;\n if (section.type !== \"section\") {\n return `Block \"${targetSectionId}\" is a ${section.type}, not a section — it has no columns.`;\n }\n const count = getColumnCount((section as SectionBlock).columns);\n if (columnIndex < 0 || columnIndex >= count) {\n return `Column ${columnIndex} is out of range for a \"${(section as SectionBlock).columns}\" section (${count} column(s)).`;\n }\n const children = section as SectionBlock;\n children.children[columnIndex] = children.children[columnIndex] || [];\n return children.children[columnIndex];\n}\n\nfunction insertAt(target: Block[], block: Block, index?: number): void {\n if (index !== undefined && index < target.length) {\n target.splice(index, 0, block);\n } else {\n target.push(block);\n }\n}\n\ntype Data = Record<string, unknown>;\n\n/** Apply one operation, returning a new document. Never mutates the input. */\nexport function applyOperation(\n content: TemplateContent,\n payload: TemplateOperationPayload,\n): OperationResult {\n const data = (payload.data ?? {}) as Data;\n\n switch (payload.operation) {\n case \"setContent\": {\n const next = data.content as TemplateContent | undefined;\n if (!next || typeof next !== \"object\" || !Array.isArray(next.blocks)) {\n return reject(\n content,\n \"setContent needs `content` with a blocks array.\",\n );\n }\n return { ok: true, content: safeClone(next) };\n }\n\n case \"updateSettings\": {\n const updates = data.settings as Partial<TemplateSettings> | undefined;\n if (!updates || typeof updates !== \"object\") {\n return reject(content, \"updateSettings needs a `settings` object.\");\n }\n const draft = safeClone(content);\n draft.settings = { ...draft.settings, ...updates };\n return { ok: true, content: draft };\n }\n\n case \"addBlock\": {\n const block = data.block as Block | undefined;\n if (!block || typeof block !== \"object\" || typeof block.id !== \"string\") {\n return reject(content, \"addBlock needs a `block` with an id.\");\n }\n const targetSectionId = data.targetSectionId as string | undefined;\n\n // Sections cannot nest inside a column — MJML forbids `mj-section` inside\n // `mj-column`, so the renderer drops them on export (issue #292). Reject\n // up front rather than lose the content silently at render time.\n if (targetSectionId && block.type === \"section\") {\n return reject(\n content,\n \"A section cannot be nested inside a section column — MJML would drop it on export. Add it at the top level instead.\",\n );\n }\n\n const draft = safeClone(content);\n if (findBlockById(draft.blocks, block.id)) {\n return reject(content, `A block with id \"${block.id}\" already exists.`);\n }\n const target = resolveTarget(\n draft,\n targetSectionId,\n (data.columnIndex as number | undefined) ?? 0,\n );\n if (typeof target === \"string\") return reject(content, target);\n\n insertAt(target, safeClone(block), data.index as number | undefined);\n return { ok: true, content: draft };\n }\n\n case \"updateBlock\": {\n const blockId = data.blockId as string | undefined;\n const updates = data.updates as Partial<Block> | undefined;\n if (!blockId) return reject(content, \"updateBlock needs a `blockId`.\");\n if (!updates || typeof updates !== \"object\") {\n return reject(content, \"updateBlock needs an `updates` object.\");\n }\n const draft = safeClone(content);\n const block = findBlockById(draft.blocks, blockId);\n if (!block) return reject(content, `No block with id \"${blockId}\".`);\n if (\"type\" in updates && updates.type !== block.type) {\n // Changing type in place would leave the block carrying the previous\n // type's fields, which no longer validate against its new subschema.\n return reject(\n content,\n `Cannot change a block's type (${block.type} → ${String(updates.type)}). Delete it and add the new block instead.`,\n );\n }\n Object.assign(block, updates);\n return { ok: true, content: draft };\n }\n\n case \"updateBlockStyle\": {\n const blockId = data.blockId as string | undefined;\n const styles = data.styles as Record<string, unknown> | undefined;\n if (!blockId) {\n return reject(content, \"updateBlockStyle needs a `blockId`.\");\n }\n if (!styles || typeof styles !== \"object\") {\n return reject(content, \"updateBlockStyle needs a `styles` object.\");\n }\n const draft = safeClone(content);\n const block = findBlockById(draft.blocks, blockId);\n if (!block) return reject(content, `No block with id \"${blockId}\".`);\n // Merge rather than replace, so a caller can set one property without\n // restating padding and every other style the block already carries.\n block.styles = {\n ...block.styles,\n ...styles,\n } as Block[\"styles\"];\n return { ok: true, content: draft };\n }\n\n case \"deleteBlock\": {\n const blockId = data.blockId as string | undefined;\n if (!blockId) return reject(content, \"deleteBlock needs a `blockId`.\");\n const draft = safeClone(content);\n const parent = findBlockParent(draft.blocks, blockId);\n if (!parent) return reject(content, `No block with id \"${blockId}\".`);\n const index = parent.blocks.findIndex((b) => b.id === blockId);\n parent.blocks.splice(index, 1);\n return { ok: true, content: draft };\n }\n\n case \"moveBlock\": {\n const blockId = data.blockId as string | undefined;\n const index = data.index as number | undefined;\n if (!blockId) return reject(content, \"moveBlock needs a `blockId`.\");\n if (typeof index !== \"number\" || index < 0) {\n return reject(content, \"moveBlock needs a non-negative `index`.\");\n }\n const targetSectionId = data.targetSectionId as string | undefined;\n\n const draft = safeClone(content);\n const parent = findBlockParent(draft.blocks, blockId);\n if (!parent) return reject(content, `No block with id \"${blockId}\".`);\n const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);\n\n if (targetSectionId && parent.blocks[oldIndex].type === \"section\") {\n return reject(\n content,\n \"A section cannot be moved into a section column — MJML would drop it on export.\",\n );\n }\n if (targetSectionId === blockId) {\n return reject(content, \"A block cannot be moved into itself.\");\n }\n\n // Resolve the target BEFORE splicing the source: an invalid target would\n // otherwise leave the block removed and unrecoverable.\n const target = resolveTarget(\n draft,\n targetSectionId,\n (data.columnIndex as number | undefined) ?? 0,\n );\n if (typeof target === \"string\") return reject(content, target);\n\n const [block] = parent.blocks.splice(oldIndex, 1);\n target.splice(index, 0, block);\n return { ok: true, content: draft };\n }\n\n default: {\n return reject(\n content,\n `Unknown operation \"${String(payload.operation)}\".`,\n );\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"src-SZZkttBC.js","names":[],"sources":["../src/operations.ts"],"sourcesContent":["// A pure reducer over the template operation vocabulary.\n//\n// `applyOperation(content, payload) -> { ok, content, error }`. The input is\n// never mutated: every operation works on a `safeClone` and returns a new\n// document, so a rejected operation leaves the caller's copy provably untouched.\n//\n// Two deliberate differences from the editor's own mutators in\n// `@templatical/core` (`src/editor.ts`), which this mirrors:\n//\n// 1. **Failure is explicit.** Core silently `return`s on an invalid operation,\n// which is right for a UI — a drag that can't drop simply doesn't. An agent\n// gets no such feedback, so a silent no-op would read as success and the\n// next instruction would build on a state that never happened. Every\n// rejection here carries a reason.\n// 2. **No collaboration locks.** `lockedBlocks` is a Cloud concern; this runs\n// against a local working file with a single writer.\n//\n// Core is FSL and cannot be imported here, so the shared invariants — above all\n// the section-into-column refusal (issue #292) — are reimplemented. That is a\n// real drift risk: a guard added to core will not appear here. `tests/\n// operations.test.ts` states each invariant against both descriptions so the\n// pair has to be reconciled deliberately.\n//\n// Note the payload keys are camelCase (`blockId`, `targetSectionId`), matching\n// `@templatical/types` and core. Cloud's MCP tools emit snake_case in their own\n// `data` payloads, so this reducer is NOT wire-compatible with Cloud broadcasts\n// and is not meant to be.\n\nimport { safeClone } from \"@templatical/types\";\nimport type {\n Block,\n ColumnLayout,\n TemplateOperationPayload,\n SectionBlock,\n TemplateContent,\n TemplateSettings,\n} from \"@templatical/types\";\n\n/** Columns a layout declares. Mirrors the helper in core's editor. */\nexport function getColumnCount(layout: ColumnLayout): number {\n if (layout === \"1\") return 1;\n if (layout === \"3\") return 3;\n return 2;\n}\n\nfunction findBlockById(blocks: Block[], id: string): Block | null {\n for (const block of blocks) {\n if (block.id === id) return block;\n if (block.type === \"section\") {\n for (const column of block.children) {\n const found = findBlockById(column, id);\n if (found) return found;\n }\n }\n }\n return null;\n}\n\ninterface ParentRef {\n blocks: Block[];\n sectionId?: string;\n columnIndex?: number;\n}\n\nfunction findBlockParent(\n blocks: Block[],\n id: string,\n parent: ParentRef = { blocks },\n): ParentRef | null {\n for (const block of blocks) {\n if (block.id === id) return parent;\n if (block.type === \"section\") {\n for (let colIdx = 0; colIdx < block.children.length; colIdx++) {\n const result = findBlockParent(block.children[colIdx], id, {\n blocks: block.children[colIdx],\n sectionId: block.id,\n columnIndex: colIdx,\n });\n if (result) return result;\n }\n }\n }\n return null;\n}\n\nexport interface OperationResult {\n ok: boolean;\n /** The new document on success; the unchanged input on failure. */\n content: TemplateContent;\n error?: string;\n}\n\nfunction reject(content: TemplateContent, error: string): OperationResult {\n return { ok: false, content, error };\n}\n\n/**\n * Resolve the array an operation targets, enforcing the column rules.\n * Returns a string on rejection so callers can surface the reason.\n */\nfunction resolveTarget(\n content: TemplateContent,\n targetSectionId: string | undefined,\n columnIndex: number,\n): Block[] | string {\n if (!targetSectionId) return content.blocks;\n\n const section = findBlockById(content.blocks, targetSectionId);\n if (!section) return `No block with id \"${targetSectionId}\".`;\n if (section.type !== \"section\") {\n return `Block \"${targetSectionId}\" is a ${section.type}, not a section — it has no columns.`;\n }\n const count = getColumnCount((section as SectionBlock).columns);\n if (columnIndex < 0 || columnIndex >= count) {\n return `Column ${columnIndex} is out of range for a \"${(section as SectionBlock).columns}\" section (${count} column(s)).`;\n }\n const children = section as SectionBlock;\n children.children[columnIndex] = children.children[columnIndex] || [];\n return children.children[columnIndex];\n}\n\nfunction insertAt(target: Block[], block: Block, index?: number): void {\n if (index !== undefined && index < target.length) {\n target.splice(index, 0, block);\n } else {\n target.push(block);\n }\n}\n\ntype Data = Record<string, unknown>;\n\n/** Apply one operation, returning a new document. Never mutates the input. */\nexport function applyOperation(\n content: TemplateContent,\n payload: TemplateOperationPayload,\n): OperationResult {\n const data = (payload.data ?? {}) as Data;\n\n switch (payload.operation) {\n case \"setContent\": {\n const next = data.content as TemplateContent | undefined;\n if (!next || typeof next !== \"object\" || !Array.isArray(next.blocks)) {\n return reject(\n content,\n \"setContent needs `content` with a blocks array.\",\n );\n }\n return { ok: true, content: safeClone(next) };\n }\n\n case \"updateSettings\": {\n const updates = data.settings as Partial<TemplateSettings> | undefined;\n if (!updates || typeof updates !== \"object\") {\n return reject(content, \"updateSettings needs a `settings` object.\");\n }\n const draft = safeClone(content);\n draft.settings = { ...draft.settings, ...updates };\n return { ok: true, content: draft };\n }\n\n case \"addBlock\": {\n const block = data.block as Block | undefined;\n if (!block || typeof block !== \"object\" || typeof block.id !== \"string\") {\n return reject(content, \"addBlock needs a `block` with an id.\");\n }\n const targetSectionId = data.targetSectionId as string | undefined;\n\n // Sections cannot nest inside a column — MJML forbids `mj-section` inside\n // `mj-column`, so the renderer drops them on export (issue #292). Reject\n // up front rather than lose the content silently at render time.\n if (targetSectionId && block.type === \"section\") {\n return reject(\n content,\n \"A section cannot be nested inside a section column — MJML would drop it on export. Add it at the top level instead.\",\n );\n }\n\n const draft = safeClone(content);\n if (findBlockById(draft.blocks, block.id)) {\n return reject(content, `A block with id \"${block.id}\" already exists.`);\n }\n const target = resolveTarget(\n draft,\n targetSectionId,\n (data.columnIndex as number | undefined) ?? 0,\n );\n if (typeof target === \"string\") return reject(content, target);\n\n insertAt(target, safeClone(block), data.index as number | undefined);\n return { ok: true, content: draft };\n }\n\n case \"updateBlock\": {\n const blockId = data.blockId as string | undefined;\n const updates = data.updates as Partial<Block> | undefined;\n if (!blockId) return reject(content, \"updateBlock needs a `blockId`.\");\n if (!updates || typeof updates !== \"object\") {\n return reject(content, \"updateBlock needs an `updates` object.\");\n }\n const draft = safeClone(content);\n const block = findBlockById(draft.blocks, blockId);\n if (!block) return reject(content, `No block with id \"${blockId}\".`);\n if (\"type\" in updates && updates.type !== block.type) {\n // Changing type in place would leave the block carrying the previous\n // type's fields, which no longer validate against its new subschema.\n return reject(\n content,\n `Cannot change a block's type (${block.type} → ${String(updates.type)}). Delete it and add the new block instead.`,\n );\n }\n Object.assign(block, updates);\n return { ok: true, content: draft };\n }\n\n case \"updateBlockStyle\": {\n const blockId = data.blockId as string | undefined;\n const styles = data.styles as Record<string, unknown> | undefined;\n if (!blockId) {\n return reject(content, \"updateBlockStyle needs a `blockId`.\");\n }\n if (!styles || typeof styles !== \"object\") {\n return reject(content, \"updateBlockStyle needs a `styles` object.\");\n }\n const draft = safeClone(content);\n const block = findBlockById(draft.blocks, blockId);\n if (!block) return reject(content, `No block with id \"${blockId}\".`);\n // Merge rather than replace, so a caller can set one property without\n // restating padding and every other style the block already carries.\n block.styles = {\n ...block.styles,\n ...styles,\n } as Block[\"styles\"];\n return { ok: true, content: draft };\n }\n\n case \"deleteBlock\": {\n const blockId = data.blockId as string | undefined;\n if (!blockId) return reject(content, \"deleteBlock needs a `blockId`.\");\n const draft = safeClone(content);\n const parent = findBlockParent(draft.blocks, blockId);\n if (!parent) return reject(content, `No block with id \"${blockId}\".`);\n const index = parent.blocks.findIndex((b) => b.id === blockId);\n parent.blocks.splice(index, 1);\n return { ok: true, content: draft };\n }\n\n case \"moveBlock\": {\n const blockId = data.blockId as string | undefined;\n const index = data.index as number | undefined;\n if (!blockId) return reject(content, \"moveBlock needs a `blockId`.\");\n if (typeof index !== \"number\" || index < 0) {\n return reject(content, \"moveBlock needs a non-negative `index`.\");\n }\n const targetSectionId = data.targetSectionId as string | undefined;\n\n const draft = safeClone(content);\n const parent = findBlockParent(draft.blocks, blockId);\n if (!parent) return reject(content, `No block with id \"${blockId}\".`);\n const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);\n\n if (targetSectionId && parent.blocks[oldIndex].type === \"section\") {\n return reject(\n content,\n \"A section cannot be moved into a section column — MJML would drop it on export.\",\n );\n }\n if (targetSectionId === blockId) {\n return reject(content, \"A block cannot be moved into itself.\");\n }\n\n // Resolve the target BEFORE splicing the source: an invalid target would\n // otherwise leave the block removed and unrecoverable.\n const target = resolveTarget(\n draft,\n targetSectionId,\n (data.columnIndex as number | undefined) ?? 0,\n );\n if (typeof target === \"string\") return reject(content, target);\n\n const [block] = parent.blocks.splice(oldIndex, 1);\n target.splice(index, 0, block);\n return { ok: true, content: draft };\n }\n\n default: {\n return reject(\n content,\n `Unknown operation \"${String(payload.operation)}\".`,\n );\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@templatical/template-tools",
3
3
  "description": "CLI and library for Templatical email templates — validate, render, edit, import, and live-preview",
4
- "version": "0.47.0",
4
+ "version": "0.48.0",
5
5
  "bin": {
6
6
  "templatical": "./dist/bin.js"
7
7
  },
8
8
  "bugs": "https://github.com/templatical/sdk/issues",
9
9
  "dependencies": {
10
- "@templatical/quality": "0.47.0",
11
- "@templatical/renderer": "0.47.0",
12
- "@templatical/types": "0.47.0",
10
+ "@templatical/quality": "0.48.0",
11
+ "@templatical/renderer": "0.48.0",
12
+ "@templatical/types": "0.48.0",
13
13
  "ajv": "^8.20.0",
14
14
  "htmlparser2": "^12.0.0",
15
15
  "liquidjs": "^10.30.0"
@@ -52,14 +52,14 @@
52
52
  "license": "MIT",
53
53
  "module": "./dist/index.js",
54
54
  "peerDependencies": {
55
- "@templatical/import-beefree": "0.47.0",
56
- "@templatical/import-chamaileon": "0.47.0",
57
- "@templatical/import-easy-email-pro": "0.47.0",
58
- "@templatical/import-html": "0.47.0",
59
- "@templatical/import-mjml": "0.47.0",
60
- "@templatical/import-stripo": "0.47.0",
61
- "@templatical/import-topol": "0.47.0",
62
- "@templatical/import-unlayer": "0.47.0",
55
+ "@templatical/import-beefree": "0.48.0",
56
+ "@templatical/import-chamaileon": "0.48.0",
57
+ "@templatical/import-easy-email-pro": "0.48.0",
58
+ "@templatical/import-html": "0.48.0",
59
+ "@templatical/import-mjml": "0.48.0",
60
+ "@templatical/import-stripo": "0.48.0",
61
+ "@templatical/import-topol": "0.48.0",
62
+ "@templatical/import-unlayer": "0.48.0",
63
63
  "mjml": "^5.4.1"
64
64
  },
65
65
  "peerDependenciesMeta": {
package/schema.json CHANGED
@@ -144,6 +144,10 @@
144
144
  "$ref": "#/definitions/BackgroundImage",
145
145
  "description": "Image behind the section's columns, rendered as `background-url` (plus size, position and repeat) on the `mj-section`. `styles.backgroundColor` is its fallback."
146
146
  },
147
+ "hero": {
148
+ "$ref": "#/definitions/SectionHero",
149
+ "description": "Draws a one-column section with a background image as an `mj-hero`: the image fills the box and the content sits on top. Ignored (the section renders as a regular one, and the `structure` linter says why) with more than one column, without an image, or with `height: \"image\"` and no image size."
150
+ },
147
151
  "wrapper": {
148
152
  "$ref": "#/definitions/SectionWrapper",
149
153
  "description": "Optional outer frame (rendered as an `mj-wrapper` around the section)."
@@ -342,6 +346,14 @@
342
346
  "repeat": {
343
347
  "type": "boolean",
344
348
  "description": "`false` paints the image once; `true` tiles it."
349
+ },
350
+ "naturalWidth": {
351
+ "type": "number",
352
+ "description": "The image file's own width in px, when known: given by the media library, measured by the editor when the author sets the URL, or read by an importer (from MJML, the hero's `background-width`, which may be the box's size rather than the file's). Only the ratio with `naturalHeight` is used: a section hero with `height: \"image\"` keeps its box at that shape. Both or neither; one without the other reads as unknown."
353
+ },
354
+ "naturalHeight": {
355
+ "type": "number",
356
+ "description": "The image file's own height in px, when known; it pairs with `naturalWidth`, and only their ratio is used. Both or neither."
345
357
  }
346
358
  },
347
359
  "required": [
@@ -378,6 +390,41 @@
378
390
  "bottom"
379
391
  ]
380
392
  },
393
+ "SectionHero": {
394
+ "type": "object",
395
+ "properties": {
396
+ "height": {
397
+ "anyOf": [
398
+ {
399
+ "type": "string",
400
+ "const": "image"
401
+ },
402
+ {
403
+ "type": "number"
404
+ }
405
+ ],
406
+ "description": "`\"image\"` makes the box as tall as the image drawn across its desktop width, and needs the image's size on `backgroundImage.naturalWidth` / `naturalHeight`; a phone keeps that height and crops the image's sides. A number is the box's fixed height in px, padding included."
407
+ },
408
+ "verticalAlign": {
409
+ "$ref": "#/definitions/HeroVerticalAlign"
410
+ }
411
+ },
412
+ "required": [
413
+ "height",
414
+ "verticalAlign"
415
+ ],
416
+ "additionalProperties": false,
417
+ "description": "Draws a one-column section as an MJML `mj-hero`: its background image fills the box (always `cover`, never tiled) and the content sits on top of it. A hero applies only to a one-column section with a background image; otherwise the section renders as a regular one."
418
+ },
419
+ "HeroVerticalAlign": {
420
+ "type": "string",
421
+ "enum": [
422
+ "top",
423
+ "middle",
424
+ "bottom"
425
+ ],
426
+ "description": "Where a hero's content sits in its box."
427
+ },
381
428
  "SectionWrapper": {
382
429
  "type": "object",
383
430
  "properties": {
@@ -603,6 +650,13 @@
603
650
  "type": "number",
604
651
  "description": "Height in pixels. Absent means the height is derived from the width, so the image keeps its aspect ratio — setting both stretches it, since email clients don't support `object-fit`."
605
652
  },
653
+ "naturalWidth": {
654
+ "type": "number",
655
+ "description": "The image file's own size in px, when known: given by the media library, read by an importer, or measured in the editor during an edit. Both or neither; one without the other reads as unknown. It drives the editor's aspect-ratio lock and is never rendered. Cleared when `src` changes without a new size alongside it."
656
+ },
657
+ "naturalHeight": {
658
+ "type": "number"
659
+ },
606
660
  "align": {
607
661
  "type": "string",
608
662
  "enum": [