@templatical/template-tools 0.48.0 → 0.49.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/README.md CHANGED
@@ -49,12 +49,12 @@ Install them wherever you run the command; they are resolved from your working d
49
49
  import {
50
50
  validateTemplate,
51
51
  runQualityLint,
52
- applyOperation,
52
+ applyOperations,
53
53
  checkCustomBlock,
54
54
  } from "@templatical/template-tools";
55
55
  import { startBridge } from "@templatical/template-tools/live";
56
56
  ```
57
57
 
58
- `checkCustomBlock` runs the same checks as `custom-block validate`, on a JSON working file or the object a definition module exports; `validateCustomBlockDefinition` runs only the structural check. The JSON Schema for a custom block definition is at `@templatical/template-tools/custom-block-schema.json`.
58
+ `applyOperations` is the operation engine `edit` runs, re-exported from `@templatical/types` with `OperationError` and `getColumnCount`. `checkCustomBlock` runs the same checks as `custom-block validate`, on a JSON working file or the object a definition module exports; `validateCustomBlockDefinition` runs only the structural check. The JSON Schema for a custom block definition is at `@templatical/template-tools/custom-block-schema.json`.
59
59
 
60
60
  MIT.
package/dist/bin.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
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";
3
+ import "./index.js";
4
4
  import { DEFAULT_PORT, WORKING_DIR, listWorkingFiles, openBrowser, pidfilePath, processAlive, readPidfile, readWorkingFile, startBridgePreferring } from "./live/index.js";
5
+ import { OperationError, applyOperations } from "@templatical/types";
5
6
  import { renderToMjml } from "@templatical/renderer";
6
7
  import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
7
8
  import { basename, dirname, extname, isAbsolute, join, resolve } from "node:path";
@@ -275,20 +276,25 @@ function parseOperations(args, cwd) {
275
276
  }
276
277
  throw new UsageError("edit needs --op '<json>' for one operation or --ops <file> for a batch.");
277
278
  }
279
+ /** An object written in the old vocabulary: `{"operation": …, "data": …}`. */
280
+ function isOldVocabulary(input) {
281
+ return typeof input === "object" && input !== null && "operation" in input && !("op" in input);
282
+ }
278
283
  function runEdit(args) {
279
284
  const file = args.positional[0];
280
285
  if (!file) throw new UsageError("edit needs a template file.");
281
286
  const cwd = flagValue(args, "cwd") ?? process.cwd();
282
287
  const operations = parseOperations(args, cwd);
283
- let content = readTemplateFile(file, cwd);
284
- for (const [index, input] of operations.entries()) {
285
- const result = applyOperation(content, {
286
- operation: input.operation,
287
- data: input.data ?? {},
288
- timestamp: Date.now()
289
- });
290
- if (!result.ok) throw new UsageError(`Operation ${index + 1} (${String(input.operation)}) was rejected: ${result.error}`);
291
- content = result.content;
288
+ const oldIndex = operations.findIndex(isOldVocabulary);
289
+ if (oldIndex !== -1) throw new UsageError(`Operation ${oldIndex + 1} uses the old {"operation", "data"} form. Write {"op": "updateBlock", "blockId": …, "set": {…}} and the like: see the operations reference.`);
290
+ const before = readTemplateFile(file, cwd);
291
+ let content;
292
+ try {
293
+ content = applyOperations(before, operations);
294
+ } catch (error) {
295
+ if (!(error instanceof OperationError)) throw error;
296
+ const refused = operations[error.opIndex];
297
+ throw new UsageError(`Operation ${error.opIndex + 1} (${String(refused?.op)}) was refused: ${error.code} — ${error.message}`);
292
298
  }
293
299
  const { valid, errors } = validateTemplate(content);
294
300
  if (!valid) throw new InvalidTemplateError(`The edited template is not structurally valid (${errors.length} error(s)); ${file} was not written.`, errors);
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","names":["FORMATS","content","USAGE"],"sources":["../src/cli/args.ts","../src/cli/output.ts","../src/cli/io.ts","../src/cli/commands/schema.ts","../src/cli/commands/validate.ts","../src/cli/resolve-optional.ts","../src/cli/mjml.ts","../src/cli/commands/render.ts","../src/cli/commands/edit.ts","../src/cli/commands/import.ts","../src/cli/commands/custom-block.ts","../src/cli/commands/live.ts","../src/bin.ts"],"sourcesContent":["// Argv parsing for the CLI. Deliberately hand-rolled: the package has no\n// runtime dependency for this, and the surface is a dozen flags.\n\n/** Flags that consume the next argv entry as their value. */\nconst VALUE_FLAGS = new Set([\n \"format\",\n \"out\",\n \"o\",\n \"op\",\n \"ops\",\n \"file\",\n \"port\",\n \"cwd\",\n \"state\",\n \"values\",\n \"custom-block\",\n \"host\",\n]);\n\nexport interface ParsedArgs {\n command?: string;\n /** Everything that is not the command and not a flag — including subcommands. */\n positional: string[];\n json: boolean;\n flags: Record<string, string | true>;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const positional: string[] = [];\n const flags: Record<string, string | true> = {};\n let json = false;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (!arg.startsWith(\"-\")) {\n positional.push(arg);\n continue;\n }\n\n const bare = arg.replace(/^--?/, \"\");\n const eq = bare.indexOf(\"=\");\n if (eq !== -1) {\n flags[bare.slice(0, eq)] = bare.slice(eq + 1);\n continue;\n }\n if (bare === \"json\") {\n json = true;\n continue;\n }\n // A value flag at the end of argv has no value; record it as present so the\n // command reports a useful usage error rather than reading `undefined`.\n flags[bare] =\n VALUE_FLAGS.has(bare) && i + 1 < argv.length ? argv[++i] : true;\n }\n\n return { command: positional.shift(), positional, json, flags };\n}\n\n/** Read a value flag as a string, or undefined when absent or valueless. */\nexport function flagValue(\n args: ParsedArgs,\n ...names: string[]\n): string | undefined {\n for (const name of names) {\n const v = args.flags[name];\n if (typeof v === \"string\") return v;\n }\n return undefined;\n}\n","// The ONLY module in this package that writes to stdout.\n//\n// `--json` promises callers exactly one parseable document on stdout, and a\n// future stdio-based caller — an MCP server reserving stdout for JSON-RPC —\n// would need that to hold just as strictly. Either way a second writer\n// breaks the contract. tests/cli-output-discipline.test.ts asserts no other\n// source file touches stdout.\n\nlet jsonMode = false;\n\nexport function setJsonMode(on: boolean): void {\n jsonMode = on;\n}\n\nexport function isJsonMode(): boolean {\n return jsonMode;\n}\n\n/**\n * Emit a command's result. Under `--json` the payload is serialized; otherwise\n * `human()` is called for the readable form — as a thunk, so building the\n * pretty output costs nothing in JSON mode.\n */\nexport function emit(payload: unknown, human: () => string): void {\n process.stdout.write(\n jsonMode ? `${JSON.stringify(payload)}\\n` : `${human()}\\n`,\n );\n}\n\n/** Diagnostics, progress and errors. Always stderr, in both modes. */\nexport function note(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n","// Shared file IO and the error→exit-code contract.\n\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\n\nexport const EXIT = {\n ok: 0,\n /** Structural errors, or quality issues of severity \"error\". */\n invalid: 1,\n /** Bad flags, missing arguments, unreadable input. */\n usage: 2,\n /** An optional dependency is not installed; the message names the install. */\n missingDep: 3,\n} as const;\n\nexport class UsageError extends Error {}\n\nexport class InvalidTemplateError extends Error {\n constructor(\n message: string,\n readonly errors: string[] = [],\n ) {\n super(message);\n }\n}\n\nexport class MissingDependencyError extends Error {\n constructor(\n readonly pkg: string,\n message: string,\n ) {\n super(message);\n }\n}\n\nexport function resolveFrom(file: string, cwd = process.cwd()): string {\n return isAbsolute(file) ? file : resolve(cwd, file);\n}\n\nexport function readTemplateFile(file: string, cwd = process.cwd()): unknown {\n const path = resolveFrom(file, cwd);\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n throw new UsageError(`Could not read ${file}`);\n }\n try {\n return JSON.parse(raw);\n } catch (err) {\n throw new UsageError(\n `${file} is not valid JSON: ${(err as Error).message}`,\n );\n }\n}\n\n/** Write a template, creating the parent directory. Returns the path written. */\nexport function writeTemplateFile(\n file: string,\n content: unknown,\n cwd = process.cwd(),\n): string {\n const path = resolveFrom(file, cwd);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(content, null, 2)}\\n`, \"utf8\");\n return path;\n}\n","import { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport { EXIT, writeTemplateFile } from \"../io\";\nimport { schema } from \"../../index\";\n\nexport function runSchema(args: ParsedArgs): number {\n const out = flagValue(args, \"out\", \"o\");\n if (out) {\n // writeTemplateFile JSON-stringifies with the same shape this command\n // wrote by hand, and creates the parent directory as a side effect.\n const path = writeTemplateFile(out, schema);\n // stdout stays empty: the caller asked for a file, and under --json a path\n // string would not be the document they are parsing for.\n note(`Wrote ${path}`);\n return EXIT.ok;\n }\n emit(schema, () => JSON.stringify(schema, null, 2));\n return EXIT.ok;\n}\n","import { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport { EXIT, readTemplateFile, UsageError } from \"../io\";\nimport { runQualityLint, validateTemplate } from \"../../index\";\n\nexport function runValidate(args: ParsedArgs): number {\n const file = args.positional[0];\n if (!file) throw new UsageError(\"validate needs a template file.\");\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n\n const data = readTemplateFile(file, cwd);\n const { valid, errors } = validateTemplate(data);\n\n if (!valid) {\n emit({ valid: false, errors, issues: [] }, () =>\n [\n `✗ Structural validation failed (${errors.length}):`,\n ...errors.map((e) => ` - ${e}`),\n ].join(\"\\n\"),\n );\n return EXIT.invalid;\n }\n\n // The linter assumes a structurally-valid template, so it runs only here.\n const quality = runQualityLint(data);\n if (quality.error) {\n note(`Quality lint could not run: ${quality.error}`);\n }\n\n const issues = quality.issues;\n const blocking = issues.filter((i) => i.severity === \"error\");\n\n emit({ valid: true, errors: [], issues }, () => {\n if (issues.length === 0) return \"✓ Valid — no quality issues\";\n const lines = issues.map(\n (i) => ` - [${i.severity}] ${i.ruleId}: ${i.message}`,\n );\n return [\n `✓ Structurally valid · ${issues.length} quality issue(s):`,\n ...lines,\n ].join(\"\\n\");\n });\n\n // A severity-\"error\" issue is a real defect, not advice, so it fails the\n // command — matching the script this replaces. Warnings and info do not.\n return blocking.length > 0 ? EXIT.invalid : EXIT.ok;\n}\n","//\n// Optional dependencies must resolve from the CONSUMER'S cwd, not from this\n// module's location. When the CLI runs via npx it lives in npm's cache, so a\n// package the user installs in their own project is invisible to the CLI's own\n// resolution — and the remediation message (\"npm install mjml\") would be\n// impossible to satisfy. Anchoring the walk at the cwd fixes that; the CLI's\n// own directory is tried second, as a fallback — not for ajv (a hard\n// `dependencies` entry, imported statically in src/validate.ts, so it never\n// passes through this function at all), but for a global install (`npm\n// install -g mjml` places the optional peer as a sibling of this package\n// under one shared global node_modules that only this anchor can reach) and\n// for this package's own tests, which anchor at a throwaway cwd outside the\n// repo and rely on this anchor climbing from inside the repo tree up to the\n// root node_modules holding the @templatical/import-* converters as root\n// devDependencies.\n//\n// Resolution deliberately does NOT go through require.resolve()/createRequire.\n// That API performs Node's CJS-invoked exports resolution, which checks the\n// condition list [\"require\", \"node\", \"default\"] and never \"import\" — so it\n// throws ERR_PACKAGE_PATH_NOT_EXPORTED for any package whose exports map\n// declares only \"import\", which is every first-party package in this monorepo\n// (ESM-only: `exports` exposes only the \"import\" condition, never \"require\"\n// or \"main\"). That map also omits \"./package.json\", so\n// require.resolve can't even read the manifest to look further. The result\n// under the old createRequire-based resolution: every @templatical/import-*\n// converter reported as \"not installed\" even when built and present on disk.\n// The ESM-native alternative, import.meta.resolve(specifier, parentURL),\n// would honor the right condition set and resolve this cwd-anchored — but its\n// second (parentURL) parameter, the one needed to anchor anywhere other than\n// this module's own URL, is documented as requiring the\n// --experimental-import-meta-resolve flag, and a published CLI that must run\n// un-flagged on Node 20 and 22 cannot bet a core resolution path on that.\n//\n// The fix walks node_modules directories by hand (findPackageDir) — a plain\n// filesystem read bypasses the exports map entirely — then reads the found\n// package's own package.json and picks its entry file directly\n// (entryFileFor), honoring exports[\".\"].import, a string exports[\".\"],\n// exports[\".\"].default, a root-string exports field, \"module\", then \"main\",\n// in that order.\n//\n// Returning null instead of throwing lets callers distinguish \"not installed\"\n// (exit 3, name the install command) from \"installed but broken\" (a real error\n// worth surfacing). Same discipline as tryLoadRenderer() in\n// packages/editor/src/utils/toMjml.ts: findPackageDir returning null is the\n// only thing that continues to the next anchor — a found package's own entry\n// resolution or import() is never caught, so a genuinely broken install\n// propagates as a real error rather than a misleading install hint.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\ninterface ExportsConditions {\n import?: string;\n default?: string;\n}\n\ninterface PackageManifest {\n exports?: string | Record<string, string | ExportsConditions>;\n module?: string;\n main?: string;\n}\n\n/**\n * Walk up from `dir` through every ancestor's node_modules looking for\n * <specifier>/package.json — the same directory-walk Node's own resolver uses\n * to locate a package, without then asking Node to apply exports-map\n * resolution. `dir` is normalized to an absolute path via `resolve()` before\n * the walk starts, so a relative anchor (e.g. \".\") climbs real ancestors —\n * unnormalized, `dirname(\".\")` is \".\" forever and the walk would give up\n * after the first miss.\n */\nfunction findPackageDir(specifier: string, dir: string): string | null {\n let current = resolve(dir);\n for (;;) {\n const candidate = join(current, \"node_modules\", specifier);\n if (existsSync(join(candidate, \"package.json\"))) return candidate;\n const parent = dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\n/**\n * Pick a found package's entry file straight from its own manifest, honoring\n * exports[\".\"].import, a string exports[\".\"], exports[\".\"].default, a\n * root-string exports field, \"module\", then \"main\" — the fields an ESM\n * consumer needs, read without Node's exports-map gating.\n */\nfunction entryFileFor(pkgDir: string): string {\n const manifest = JSON.parse(\n readFileSync(join(pkgDir, \"package.json\"), \"utf8\"),\n ) as PackageManifest;\n\n const dot =\n manifest.exports && typeof manifest.exports === \"object\"\n ? manifest.exports[\".\"]\n : undefined;\n const conditions = dot && typeof dot === \"object\" ? dot : undefined;\n // \"exports\": \"./dist/index.js\" — no \".\" key at all. PackageManifest already\n // types this shape; the code just never read it.\n const rootExports =\n typeof manifest.exports === \"string\" ? manifest.exports : undefined;\n\n const entry =\n conditions?.import ??\n (typeof dot === \"string\" ? dot : undefined) ??\n conditions?.default ??\n rootExports ??\n manifest.module ??\n manifest.main;\n\n // typeof, not just truthiness: the tsup/tsdown types-first shape nests\n // exports[\".\"].import as a further conditions object ({ types, default }),\n // not a string. Without this check that object flows into join() below and\n // throws a raw `TypeError: The \"path\" argument must be of type string`\n // instead of this function's own, actionable error.\n if (!entry || typeof entry !== \"string\") {\n throw new Error(\n `${pkgDir} has no resolvable entry point (exports/module/main).`,\n );\n }\n return join(pkgDir, entry);\n}\n\nexport async function resolveOptional<T>(\n specifier: string,\n cwd: string = process.cwd(),\n): Promise<T | null> {\n const anchors = [cwd, dirname(fileURLToPath(import.meta.url))];\n\n for (const anchor of anchors) {\n const pkgDir = findPackageDir(specifier, anchor);\n if (!pkgDir) continue; // not found from this anchor — try the next\n // Deliberately outside the try below: a manifest that names no usable\n // entry is a packaging fault the caller can act on, and it keeps throwing.\n const resolved = entryFileFor(pkgDir);\n try {\n return (await import(pathToFileURL(resolved).href)) as T;\n } catch {\n // The manifest is fine but the file it names isn't there — a workspace\n // package whose dist/ hasn't been built is the everyday case. \"Optional\"\n // has to cover this, or `import --list-formats`, whose whole job is to\n // report which converters resolve, crashes on the first one that doesn't.\n // Keep looking: a later anchor may hold a complete copy.\n continue;\n }\n }\n return null;\n}\n","// The optional `mjml` peer, resolved from the user's cwd first. The SDK\n// bundles no MJML compiler by design, so a missing peer is either skipped\n// (validate) or a hard stop with an install hint (render --format html).\n//\n// The mjml package's top-level function is `async`, so it always returns a\n// Promise even though the underlying compile is synchronous.\n\nimport { MissingDependencyError } from \"./io\";\nimport { resolveOptional } from \"./resolve-optional\";\n\nexport type Mjml2Html = (\n mjml: string,\n options?: Record<string, unknown>,\n) => Promise<{ html: string; errors: unknown[] }>;\n\nexport async function loadMjml(cwd: string): Promise<Mjml2Html | null> {\n const mod = await resolveOptional<{ default: Mjml2Html }>(\"mjml\", cwd);\n return mod ? mod.default : null;\n}\n\nexport async function requireMjml(\n cwd: string,\n purpose: string,\n): Promise<Mjml2Html> {\n const mjml = await loadMjml(cwd);\n if (!mjml) {\n throw new MissingDependencyError(\n \"mjml\",\n `${purpose} needs the optional \\`mjml\\` package, which isn't installed.\\n npm install mjml`,\n );\n }\n return mjml;\n}\n","import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { renderToMjml } from \"@templatical/renderer\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport {\n EXIT,\n InvalidTemplateError,\n readTemplateFile,\n resolveFrom,\n UsageError,\n} from \"../io\";\nimport { requireMjml } from \"../mjml\";\nimport { validateTemplate } from \"../../index\";\n\nconst FORMATS = new Set([\"mjml\", \"html\"]);\n\nexport async function runRender(args: ParsedArgs): Promise<number> {\n const file = args.positional[0];\n if (!file) throw new UsageError(\"render needs a template file.\");\n\n const format = flagValue(args, \"format\") ?? \"mjml\";\n if (!FORMATS.has(format)) {\n throw new UsageError(`Unknown --format \"${format}\". Use mjml or html.`);\n }\n\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n const data = readTemplateFile(file, cwd);\n\n // A structurally invalid template must fail with the error list (exit 1),\n // not surface as a renderer-internals crash (exit 2) — edit.ts validates\n // for the same reason before it writes.\n const { valid, errors } = validateTemplate(data);\n if (!valid) {\n throw new InvalidTemplateError(\n `${file} is not structurally valid (${errors.length} error(s)).`,\n errors,\n );\n }\n\n const content = data as TemplateContent;\n const mjml = await renderToMjml(content);\n\n let output = mjml;\n if (format === \"html\") {\n const compile = await requireMjml(cwd, \"Rendering HTML\");\n output = (await compile(mjml, { validationLevel: \"soft\" })).html;\n }\n\n const out = flagValue(args, \"out\", \"o\");\n if (out) {\n const path = resolveFrom(out, cwd);\n // render writes raw text, not JSON, so it can't reuse writeTemplateFile —\n // create the parent directory directly instead.\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, output, \"utf8\");\n note(`Wrote ${path}`);\n return EXIT.ok;\n }\n\n emit({ format, output }, () => output);\n return EXIT.ok;\n}\n","import type {\n TemplateContent,\n TemplateOperationPayload,\n} from \"@templatical/types\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit } from \"../output\";\nimport {\n EXIT,\n InvalidTemplateError,\n readTemplateFile,\n UsageError,\n writeTemplateFile,\n} from \"../io\";\nimport { applyOperation, validateTemplate } from \"../../index\";\n\n/** What a caller supplies; `timestamp` is ours to fill. */\ninterface OperationInput {\n operation: TemplateOperationPayload[\"operation\"];\n data?: Record<string, unknown>;\n}\n\nfunction parseOperations(args: ParsedArgs, cwd: string): OperationInput[] {\n const inline = flagValue(args, \"op\");\n const batchFile = flagValue(args, \"ops\");\n // !== undefined, not truthy: flagValue returns \"\" for an explicitly-empty\n // flag, and a truthy check would let --op \"\" --ops <file> silently run the\n // batch instead of reporting that both were passed.\n if (inline !== undefined && batchFile !== undefined) {\n throw new UsageError(\"Pass either --op or --ops, not both.\");\n }\n if (inline) {\n try {\n return [JSON.parse(inline) as OperationInput];\n } catch (err) {\n throw new UsageError(`--op is not valid JSON: ${(err as Error).message}`);\n }\n }\n if (batchFile) {\n const parsed = readTemplateFile(batchFile, cwd);\n if (!Array.isArray(parsed)) {\n throw new UsageError(\"--ops must point at a JSON array of operations.\");\n }\n return parsed as OperationInput[];\n }\n throw new UsageError(\n \"edit needs --op '<json>' for one operation or --ops <file> for a batch.\",\n );\n}\n\nexport function runEdit(args: ParsedArgs): number {\n const file = args.positional[0];\n if (!file) throw new UsageError(\"edit needs a template file.\");\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n\n const operations = parseOperations(args, cwd);\n let content = readTemplateFile(file, cwd) as TemplateContent;\n\n // All-or-nothing: applyOperation returns the unchanged input on rejection, so\n // stopping at the first failure and discarding needs no rollback. Nothing is\n // written until every operation has succeeded.\n for (const [index, input] of operations.entries()) {\n const result = applyOperation(content, {\n operation: input.operation,\n data: input.data ?? {},\n timestamp: Date.now(),\n });\n if (!result.ok) {\n throw new UsageError(\n `Operation ${index + 1} (${String(input.operation)}) was rejected: ${result.error}`,\n );\n }\n content = result.content;\n }\n\n // An operation can produce a structurally invalid document, so validate\n // before writing: the working file must always be valid.\n const { valid, errors } = validateTemplate(content);\n if (!valid) {\n throw new InvalidTemplateError(\n `The edited template is not structurally valid (${errors.length} error(s)); ${file} was not written.`,\n errors,\n );\n }\n\n const path = writeTemplateFile(file, content, cwd);\n emit(\n { applied: operations.length, file: path },\n () => `Applied ${operations.length} operation(s) to ${path}`,\n );\n return EXIT.ok;\n}\n","// The format registry. A new @templatical/import-* converter is ONE entry here\n// and nothing else: reference/import.md documents the routing rule rather than\n// the list, and `--list-formats` answers what is resolvable at runtime. That is\n// what lets a new importer ship without a skill edit.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { basename, extname } from \"node:path\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit } from \"../output\";\nimport {\n EXIT,\n MissingDependencyError,\n resolveFrom,\n UsageError,\n writeTemplateFile,\n} from \"../io\";\nimport { resolveOptional } from \"../resolve-optional\";\nimport { WORKING_DIR } from \"../../live/index\";\n\ninterface FormatSpec {\n pkg: string;\n /** The converter's exported function name. */\n fn: string;\n /**\n * What the converter is handed. `json` parses the source first, `text` passes\n * it through, and `stripo` unpacks a `{ html, css }` envelope — Stripo is the\n * one format whose export may arrive as either shape.\n */\n input: \"json\" | \"text\" | \"stripo\";\n}\n\nexport const FORMATS: Record<string, FormatSpec> = {\n unlayer: {\n pkg: \"@templatical/import-unlayer\",\n fn: \"convertUnlayerTemplate\",\n input: \"json\",\n },\n beefree: {\n pkg: \"@templatical/import-beefree\",\n fn: \"convertBeeFreeTemplate\",\n input: \"json\",\n },\n stripo: {\n pkg: \"@templatical/import-stripo\",\n fn: \"convertStripoTemplate\",\n input: \"stripo\",\n },\n topol: {\n pkg: \"@templatical/import-topol\",\n fn: \"convertTopolTemplate\",\n input: \"json\",\n },\n chamaileon: {\n pkg: \"@templatical/import-chamaileon\",\n fn: \"convertChamaileonTemplate\",\n input: \"json\",\n },\n \"easy-email-pro\": {\n pkg: \"@templatical/import-easy-email-pro\",\n fn: \"convertEasyEmailProTemplate\",\n input: \"json\",\n },\n mjml: {\n pkg: \"@templatical/import-mjml\",\n fn: \"convertMjmlTemplate\",\n input: \"text\",\n },\n html: {\n pkg: \"@templatical/import-html\",\n fn: \"convertHtmlTemplate\",\n input: \"text\",\n },\n};\n\n/** Strip a tag and its contents, so class scanning never reads CSS or script. */\nfunction withoutElements(html: string, tag: string): string {\n const open = `<${tag}`;\n const close = `</${tag}`;\n const lower = html.toLowerCase();\n let out = \"\";\n let pos = 0;\n while (pos < html.length) {\n const start = lower.indexOf(open, pos);\n if (start === -1) {\n out += html.slice(pos);\n break;\n }\n const next = lower[start + open.length];\n // `<style` matches `<styles>` too unless the next char ends the tag name.\n if (next !== undefined && /[a-z0-9-]/.test(next)) {\n out += html.slice(pos, start + open.length);\n pos = start + open.length;\n continue;\n }\n out += html.slice(pos, start);\n const gt = html.indexOf(\">\", start);\n if (gt === -1) break;\n const closeAt = lower.indexOf(close, gt + 1);\n if (closeAt === -1) break;\n const closeGt = html.indexOf(\">\", closeAt);\n if (closeGt === -1) break;\n out += \" \";\n pos = closeGt + 1;\n }\n return out;\n}\n\n/** Every class token in the markup, ignoring <style> and <script> contents. */\nfunction markupClassTokens(html: string): string[] {\n const stripped = withoutElements(withoutElements(html, \"style\"), \"script\");\n const tokens: string[] = [];\n const re = /\\bclass\\s*=\\s*([\"'])([^\"']*)\\1/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(stripped))) {\n tokens.push(...m[2].trim().split(/\\s+/).filter(Boolean));\n }\n return tokens;\n}\n\n/** Stripo's own class prefixes, which survive both of its export shapes. */\nfunction looksLikeStripoHtml(html: unknown): boolean {\n if (typeof html !== \"string\" || html.trim().length === 0) return false;\n const tokens = markupClassTokens(html);\n if (\n tokens.some(\n (t) =>\n t === \"esd-stripe\" ||\n t === \"esd-structure\" ||\n t === \"esd-container-frame\" ||\n t.startsWith(\"esd-block-\"),\n )\n ) {\n return true;\n }\n return tokens.some(\n (t) =>\n t === \"es-wrapper\" || t === \"es-content-body\" || t === \"es-header-body\",\n );\n}\n\n/** Easy Email Pro marks its own nodes `standard-*`; OSS Easy Email does not. */\nfunction hasStandardType(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n const n = node as { type?: unknown; children?: unknown };\n if (typeof n.type === \"string\" && n.type.startsWith(\"standard-\")) return true;\n if (Array.isArray(n.children)) return n.children.some(hasStandardType);\n return false;\n}\n\n/** Guess the source format, or null when the caller must pass --format. */\nexport function detectFormat(fileName: string, content: string): string | null {\n const ext = extname(fileName).toLowerCase();\n const trimmed = content.trimStart();\n\n // MJML is decided before both the extension check and the generic `<` branch:\n // an MJML document saved as .html is still MJML, and reading it as HTML\n // silently produces a table-soup import that looks like a bad converter.\n if (ext === \".mjml\") return \"mjml\";\n if (/^<(\\?xml[^>]*\\?>\\s*)?<?\\s*mjml[\\s>]/i.test(trimmed)) return \"mjml\";\n if (/^<\\s*mj-body[\\s>]/i.test(trimmed)) return \"mjml\";\n\n // Stripo's compiled File→HTML and its plugin storage are both `.html` (or a\n // JSON `{ html, css }` blob), so the class check must run before the generic\n // html branch or every Stripo export imports as table soup.\n if (looksLikeStripoHtml(content)) return \"stripo\";\n\n if (ext === \".html\" || ext === \".htm\") return \"html\";\n if (trimmed.startsWith(\"<\")) return \"html\";\n if (trimmed.startsWith(\"{\")) {\n let obj: {\n body?: { rows?: unknown; type?: unknown };\n page?: { rows?: unknown };\n tagName?: unknown;\n content?: { type?: unknown };\n type?: unknown;\n html?: unknown;\n };\n try {\n obj = JSON.parse(content);\n } catch {\n return null;\n }\n // Unlayer designs come from editor.saveDesign(): { body: { rows } }.\n if (obj?.body?.rows) return \"unlayer\";\n // BeeFree templates: { page: { rows } }.\n if (obj?.page?.rows) return \"beefree\";\n // Topol designs are an MJML-shaped tree rooted at the global style.\n if (obj?.tagName === \"mj-global-style\") return \"topol\";\n // Chamaileon getDocument(): { body: { type: \"body\" } }. Unlayer's\n // { body: { rows } } is matched above, so the order between them matters.\n if (obj?.body?.type === \"body\") return \"chamaileon\";\n // Easy Email Pro persist: { content: { type: \"page\" } } or a bare page,\n // distinguished from OSS Easy Email by its `standard-*` node types.\n const page =\n obj?.content?.type === \"page\"\n ? obj.content\n : obj?.type === \"page\"\n ? obj\n : null;\n if (page && hasStandardType(page)) return \"easy-email-pro\";\n // A plugin host often persists the whole getTemplateData() object.\n if (typeof obj?.html === \"string\" && looksLikeStripoHtml(obj.html)) {\n return \"stripo\";\n }\n return null;\n }\n return null;\n}\n\n/** Split a Stripo source into html + css, whichever shape it arrived in. */\nexport function unpackStripoSource(source: string): {\n html: string;\n css?: string;\n} {\n const trimmed = source.trimStart();\n if (trimmed.startsWith(\"{\")) {\n try {\n const obj = JSON.parse(source) as { html?: unknown; css?: unknown };\n if (typeof obj?.html === \"string\") {\n return {\n html: obj.html,\n css: typeof obj.css === \"string\" ? obj.css : undefined,\n };\n }\n } catch {\n // Not an envelope after all — treat the whole thing as markup.\n }\n }\n return { html: source };\n}\n\n/** The stylesheet a plugin host wrote beside an HTML export, if there is one. */\nfunction siblingCss(sourcePath: string, source: string): string | undefined {\n // A JSON envelope carries its own css; only a bare .html file has a sibling.\n if (source.trimStart().startsWith(\"{\")) return undefined;\n const cssPath = sourcePath.replace(/\\.[^.]+$/, \".css\");\n if (cssPath === sourcePath || !existsSync(cssPath)) return undefined;\n return readFileSync(cssPath, \"utf8\");\n}\n\nexport interface ReportCounts {\n total: number;\n converted: number;\n approximated: number;\n htmlFallback: number;\n skipped: number;\n warnings: string[];\n}\n\n/**\n * Status counts from a converter's report. Derived from `report.entries` (every\n * entry carries a `status`) rather than each package's own `summary` shape, so\n * it works identically across all three converters and any future one.\n */\nexport function summarizeReport(report: unknown): ReportCounts {\n const r = report as\n { entries?: Array<{ status?: string }>; warnings?: string[] } | undefined;\n const counts: ReportCounts = {\n total: 0,\n converted: 0,\n approximated: 0,\n htmlFallback: 0,\n skipped: 0,\n warnings: r?.warnings ?? [],\n };\n for (const e of r?.entries ?? []) {\n counts.total++;\n if (e.status === \"converted\") counts.converted++;\n else if (e.status === \"approximated\") counts.approximated++;\n else if (e.status === \"html-fallback\") counts.htmlFallback++;\n else if (e.status === \"skipped\") counts.skipped++;\n }\n return counts;\n}\n\nasync function listFormats(cwd: string): Promise<number> {\n const formats: Array<{\n format: string;\n package: string;\n available: boolean;\n }> = [];\n for (const [format, spec] of Object.entries(FORMATS)) {\n formats.push({\n format,\n package: spec.pkg,\n available: (await resolveOptional(spec.pkg, cwd)) !== null,\n });\n }\n emit({ formats }, () =>\n formats\n .map(\n (f) =>\n ` ${f.format.padEnd(14)} ${f.package}${f.available ? \"\" : \" (not installed)\"}`,\n )\n .join(\"\\n\"),\n );\n return EXIT.ok;\n}\n\nexport async function runImport(args: ParsedArgs): Promise<number> {\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n if (args.flags[\"list-formats\"]) return listFormats(cwd);\n\n const file = args.positional[0];\n if (!file) throw new UsageError(\"import needs a source file.\");\n const path = resolveFrom(file, cwd);\n\n let source: string;\n try {\n source = readFileSync(path, \"utf8\");\n } catch {\n throw new UsageError(`Could not read ${file}`);\n }\n\n const known = Object.keys(FORMATS).join(\", \");\n const requested = flagValue(args, \"format\");\n if (requested && !FORMATS[requested]) {\n throw new UsageError(\n `Unknown --format \"${requested}\". Known formats: ${known}.`,\n );\n }\n const format = requested ?? detectFormat(path, source);\n if (!format) {\n throw new UsageError(\n `Could not detect the format of ${file}. Pass --format with one of: ${known}.`,\n );\n }\n\n const spec = FORMATS[format];\n const mod = await resolveOptional<Record<string, unknown>>(spec.pkg, cwd);\n if (!mod) {\n throw new MissingDependencyError(\n spec.pkg,\n `Importing ${format} needs ${spec.pkg}, which isn't installed.\\n npm install ${spec.pkg}`,\n );\n }\n const convert = mod[spec.fn] as (\n input: unknown,\n options?: unknown,\n ) => {\n content: unknown;\n report: unknown;\n };\n\n let content: unknown;\n let report: unknown;\n if (spec.input === \"stripo\") {\n // Plugin storage keeps the stylesheet out of the markup — either beside the\n // html in one JSON envelope, or as a sibling .css file the host wrote next\n // to it. Without the CSS every block imports unstyled, which reads as a\n // broken converter rather than a missing file.\n const unpacked = unpackStripoSource(source);\n const css = unpacked.css ?? siblingCss(path, source);\n ({ content, report } = convert(unpacked.html, css ? { css } : undefined));\n } else {\n const input = spec.input === \"json\" ? JSON.parse(source) : source;\n ({ content, report } = convert(input));\n }\n\n const outName = flagValue(args, \"out\") ?? basename(path, extname(path));\n const written = writeTemplateFile(\n `${WORKING_DIR}/${outName}.json`,\n content,\n cwd,\n );\n\n const counts = summarizeReport(report);\n const lossy = counts.htmlFallback + counts.skipped > 0;\n emit({ format, file: written, report: counts }, () =>\n [\n `Imported ${format} to ${written}`,\n ` ${counts.converted} converted, ${counts.approximated} approximated, ${counts.htmlFallback} html fallback, ${counts.skipped} skipped`,\n ...counts.warnings.map((w) => ` ! ${w}`),\n lossy\n ? \" Import is lossy. Open it in live mode and refine the fallback blocks.\"\n : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n );\n return EXIT.ok;\n}\n","import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport {\n EXIT,\n InvalidTemplateError,\n readTemplateFile,\n resolveFrom,\n UsageError,\n} from \"../io\";\nimport { loadMjml, requireMjml } from \"../mjml\";\nimport {\n buildSpecimen,\n buildSpecimenTemplate,\n checkCustomBlock,\n checkRecipe,\n renderSpecimenMjml,\n runRecipe,\n SPECIMEN_STATES,\n validateCustomBlockDefinition,\n type CustomBlockIssue,\n type CustomBlockWorkingFile,\n type SpecimenState,\n} from \"../../custom-block\";\n\nconst USAGE =\n \"Use `custom-block validate <file>`, `custom-block render <file>` or `custom-block fetch <file>`.\";\n\nfunction formatIssues(issues: CustomBlockIssue[]): string {\n return issues\n .map(\n (i) =>\n ` - [${i.severity}] ${i.ruleId}${i.path ? ` ${i.path}` : \"\"}: ${i.message}`,\n )\n .join(\"\\n\");\n}\n\n/** `--values '<json>'`: field values a recipe runs with, over the defaults. */\nexport function parseValuesFlag(\n raw: string | undefined,\n): Record<string, unknown> {\n if (raw === undefined) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new UsageError(\"--values must be a JSON object.\");\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new UsageError(\"--values must be a JSON object.\");\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction loadValid(file: string, cwd: string): CustomBlockWorkingFile {\n const data = readTemplateFile(file, cwd);\n const r = validateCustomBlockDefinition(data);\n if (!r.valid) {\n throw new InvalidTemplateError(\n `${file} is not a valid custom block definition.`,\n r.issues.filter((i) => i.severity === \"error\").map((i) => i.message),\n );\n }\n return data as CustomBlockWorkingFile;\n}\n\nexport async function runCustomBlock(args: ParsedArgs): Promise<number> {\n const sub = args.positional[0];\n const file = args.positional[1];\n if (sub !== \"validate\" && sub !== \"render\" && sub !== \"fetch\") {\n throw new UsageError(`Unknown \"custom-block ${sub ?? \"\"}\". ${USAGE}`);\n }\n if (!file)\n throw new UsageError(`custom-block ${sub} needs a definition file.`);\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n\n if (sub === \"validate\") {\n const mjml = await loadMjml(cwd);\n const result = await checkCustomBlock(readTemplateFile(file, cwd), {\n compileMjml: mjml\n ? async (m) => ({\n errors: (await mjml(m, { validationLevel: \"soft\" })).errors,\n })\n : undefined,\n });\n if (!mjml)\n note(\"`mjml` isn't installed, so the HTML compile check was skipped.\");\n emit(result, () =>\n result.issues.length === 0\n ? \"✓ Valid custom block — no issues\"\n : `${result.valid ? \"✓ Valid\" : \"✗ Invalid\"} · ${result.issues.length} issue(s):\\n${formatIssues(result.issues)}`,\n );\n return result.valid ? EXIT.ok : EXIT.invalid;\n }\n\n const def = loadValid(file, cwd);\n\n if (sub === \"render\") {\n const state = flagValue(args, \"state\");\n if (\n state !== undefined &&\n !(SPECIMEN_STATES as readonly string[]).includes(state)\n ) {\n throw new UsageError(\n `Unknown --state \"${state}\". Use one of: ${SPECIMEN_STATES.join(\", \")}.`,\n );\n }\n // A state the definition doesn't produce would render a sheet without the\n // block, which reads as a block that renders nothing.\n if (state !== undefined) {\n const states = buildSpecimen(def).map((i) => i.state);\n if (!(states as string[]).includes(state)) {\n throw new UsageError(\n `${file} has no \"${state}\" state: it has no ${state === \"flipped\" ? \"boolean\" : \"repeatable\"} field. Its states: ${states.join(\", \")}.`,\n );\n }\n }\n const format = flagValue(args, \"format\") ?? \"mjml\";\n if (format !== \"mjml\" && format !== \"html\")\n throw new UsageError(`Unknown --format \"${format}\". Use mjml or html.`);\n let output: string;\n try {\n output = await renderSpecimenMjml(\n def,\n buildSpecimenTemplate(\n def,\n undefined,\n state as SpecimenState | undefined,\n ),\n );\n } catch (err) {\n throw new InvalidTemplateError(`${file} does not render.`, [\n (err as Error).message,\n ]);\n }\n if (format === \"html\")\n output = (\n await (\n await requireMjml(cwd, \"Rendering HTML\")\n )(output, { validationLevel: \"soft\" })\n ).html;\n const out = flagValue(args, \"out\", \"o\");\n if (out) {\n const path = resolveFrom(out, cwd);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, output, \"utf8\");\n note(`Wrote ${path}`);\n return EXIT.ok;\n }\n emit({ format, output }, () => output);\n return EXIT.ok;\n }\n\n // sub === \"fetch\"\n if (!def.dataSourcePreview)\n throw new UsageError(`${file} has no \\`dataSourcePreview\\` to run.`);\n const recipeErrors = checkRecipe(def).filter((i) => i.severity === \"error\");\n if (recipeErrors.length > 0) {\n throw new InvalidTemplateError(\n `${file} has a dataSourcePreview the bridge would refuse to run.`,\n recipeErrors.map((i) => `${i.ruleId}: ${i.message}`),\n );\n }\n const overrides = parseValuesFlag(flagValue(args, \"values\"));\n const defaults = buildSpecimen(def)[0].fieldValues;\n const result = await runRecipe(def.dataSourcePreview, {\n ...defaults,\n ...overrides,\n });\n emit(result, () =>\n result.ok\n ? `✓ ${result.status} · ${JSON.stringify(result.values, null, 2)}${result.unmapped.length ? `\\nUnmapped: ${result.unmapped.join(\", \")}` : \"\"}`\n : `✗ ${result.error}`,\n );\n return result.ok ? EXIT.ok : EXIT.invalid;\n}\n","import { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport { EXIT, resolveFrom, UsageError } from \"../io\";\nimport { parseValuesFlag } from \"./custom-block\";\nimport {\n DEFAULT_PORT,\n listWorkingFiles,\n type FetchSummary,\n type LiveMode,\n type PidfileInfo,\n openBrowser,\n pidfilePath,\n processAlive,\n readPidfile,\n readWorkingFile,\n startBridgePreferring,\n WORKING_DIR,\n} from \"../../live/index\";\n\n/**\n * The first title block's text in document order, as a hint for `list`.\n *\n * It must descend into a section's columns, not scan top-level blocks only:\n * templates put their content inside sections (that is the documented\n * structure), so a top-level scan finds nothing for a real template. Measured\n * across all five of the templatical skill's examples — event-invite, newsletter,\n * product-sale, receipt, welcome — none has a top-level title block, so a\n * shallow version of this returns null every time and the hint is dead code.\n */\nfunction titleHint(content: unknown): string | null {\n return findTitle((content as { blocks?: unknown })?.blocks);\n}\n\nfunction findTitle(blocks: unknown): string | null {\n if (!Array.isArray(blocks)) return null;\n for (const block of blocks as Array<{\n type?: string;\n content?: string;\n children?: unknown[];\n }>) {\n if (block?.type === \"title\" && typeof block.content === \"string\") {\n return block.content;\n }\n if (block?.type === \"section\" && Array.isArray(block.children)) {\n for (const column of block.children) {\n const hit = findTitle(column);\n if (hit) return hit;\n }\n }\n }\n return null;\n}\n\nexport function runList(args: ParsedArgs): number {\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n const templates = listWorkingFiles(cwd).map((name) => ({\n name,\n title: titleHint(readWorkingFile(join(cwd, WORKING_DIR, name))),\n }));\n emit({ templates }, () =>\n templates.length === 0\n ? `No templates in ${WORKING_DIR}/`\n : templates\n .map((t) => ` ${t.name}${t.title ? ` - ${t.title}` : \"\"}`)\n .join(\"\\n\"),\n );\n return EXIT.ok;\n}\n\nasync function postTo(\n port: number,\n path: string,\n body?: unknown,\n): Promise<Response> {\n return fetch(`http://localhost:${port}${path}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n}\n\n/** The fetched state's outcome, as one line of reload or start output. */\nfunction fetchLine(fetch: FetchSummary): string {\n if (!fetch.ok) return `Fetched state: the request failed. ${fetch.error}`;\n return fetch.unmapped.length > 0\n ? `Fetched state: filled from the API; not in the response: ${fetch.unmapped.join(\", \")}.`\n : \"Fetched state: filled from the API.\";\n}\n\nexport async function runLive(args: ParsedArgs): Promise<number> {\n const sub = args.positional[0];\n const cwd = resolveFrom(flagValue(args, \"cwd\") ?? \".\", process.cwd());\n\n if (sub === \"reload\" || sub === \"stop\") {\n const info = readPidfile(cwd);\n if (!info || !processAlive(info.pid)) {\n throw new UsageError(\n `No live server is running here (no live pidfile at ${WORKING_DIR}/live-server.pid).`,\n );\n }\n if (sub === \"reload\") {\n const consumeAnnotations = args.flags[\"consume-annotations\"] === true;\n const res = await postTo(\n info.port,\n \"/reload\",\n consumeAnnotations ? { consumeAnnotations: true } : undefined,\n );\n const body = (await res.json().catch(() => ({}))) as {\n ok?: boolean;\n clients?: number;\n consumed?: boolean;\n mode?: LiveMode;\n error?: string;\n fetch?: FetchSummary;\n };\n const clients = body.clients ?? 0;\n const consumed = body.consumed === true;\n // The bridge's error already names what failed and what to run; a\n // refused request (a 403 from a rewritten Host, say) has no body at all.\n if (res.status >= 400 || body.ok === false) {\n const reason =\n body.error ??\n (res.status >= 400\n ? `The live server refused the reload (${res.status}).`\n : \"The live server reported a failed reload.\");\n const error = `${reason} Nothing was pushed.`;\n emit(\n { reloaded: false, ok: false, clients, consumed, error },\n () => error,\n );\n return EXIT.invalid;\n }\n const fetch = body.fetch;\n emit(\n {\n reloaded: true,\n ok: true,\n clients,\n consumed,\n ...(fetch ? { fetch } : {}),\n },\n () =>\n [\n body.mode === \"custom-block\"\n ? `Pushed the custom block to ${clients} page(s).`\n : `Pushed the working file to ${clients} connected page(s).`,\n fetch ? fetchLine(fetch) : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n );\n return EXIT.ok;\n }\n try {\n process.kill(info.pid, \"SIGTERM\");\n } catch {\n /* already gone */\n }\n rmSync(pidfilePath(cwd), { force: true });\n emit({ stopped: true }, () => \"Stopped the live server.\");\n return EXIT.ok;\n }\n\n if (sub !== undefined) {\n throw new UsageError(\n `Unknown \"live ${sub}\". Use \\`live\\`, \\`live reload\\` or \\`live stop\\`.`,\n );\n }\n\n // start\n const portFlag = flagValue(args, \"port\");\n const preferredPort = portFlag ? Number(portFlag) : DEFAULT_PORT;\n const file = flagValue(args, \"file\");\n const customBlock = flagValue(args, \"custom-block\");\n const host = flagValue(args, \"host\");\n if (customBlock && file) {\n throw new UsageError(\"Pass either --file or --custom-block, not both.\");\n }\n if (host && !customBlock) {\n throw new UsageError(\"--host only applies with --custom-block.\");\n }\n const fetchOn = args.flags.fetch === true;\n if (fetchOn && !customBlock) {\n throw new UsageError(\"--fetch only applies with --custom-block.\");\n }\n const valuesRaw = flagValue(args, \"values\");\n if (valuesRaw !== undefined && !fetchOn) {\n throw new UsageError(\"--values only applies with --fetch.\");\n }\n const fetchValues = fetchOn ? parseValuesFlag(valuesRaw) : undefined;\n const abs = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p));\n const requested: Required<Pick<PidfileInfo, \"mode\" | \"path\">> &\n Pick<PidfileInfo, \"host\" | \"fetch\"> = {\n mode: customBlock ? \"custom-block\" : \"template\",\n path: abs(customBlock ?? file ?? join(WORKING_DIR, \"template.json\")),\n ...(host ? { host: abs(host) } : {}),\n ...(fetchValues ? { fetch: fetchValues } : {}),\n };\n\n const existing = readPidfile(cwd);\n if (existing && processAlive(existing.pid)) {\n // A pidfile without `mode` came from a template-only bridge; one without\n // `path` can't be compared, so only its mode is.\n const mode = existing.mode ?? \"template\";\n if (\n mode !== requested.mode ||\n (existing.path !== undefined && existing.path !== requested.path) ||\n (mode === \"custom-block\" &&\n (existing.host !== requested.host ||\n JSON.stringify(existing.fetch) !== JSON.stringify(requested.fetch)))\n ) {\n throw new UsageError(\n `A live server is already running for ${existing.path ?? \"another file\"} (${mode}). Run \\`templatical live stop\\` first.`,\n );\n }\n emit(\n {\n url: `http://localhost:${existing.port}/`,\n pid: existing.pid,\n alreadyRunning: true,\n },\n () =>\n `Live server already running (pid ${existing.pid}) at http://localhost:${existing.port}/`,\n );\n return EXIT.ok;\n }\n if (existing) rmSync(pidfilePath(cwd), { force: true }); // stale\n\n const handle = await startBridgePreferring({\n cwd,\n preferredPort,\n file,\n customBlock,\n host,\n ...(fetchValues ? { fetch: { values: fetchValues } } : {}),\n });\n\n mkdirSync(dirname(pidfilePath(cwd)), { recursive: true });\n writeFileSync(\n pidfilePath(cwd),\n JSON.stringify({\n pid: process.pid,\n port: handle.port,\n ...requested,\n path: handle.workingPath,\n } satisfies PidfileInfo),\n \"utf8\",\n );\n\n const cleanup = () => {\n rmSync(pidfilePath(cwd), { force: true });\n handle.close().finally(() => process.exit(EXIT.ok));\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n // Emit before blocking: the caller needs the URL and the working path now,\n // not when the server shuts down.\n const fetch = handle.lastFetch();\n emit(\n {\n url: handle.url,\n port: handle.port,\n preferredPort,\n fellBack: handle.fellBack,\n workingFile: handle.workingPath,\n customBlock: customBlock ? handle.workingPath : undefined,\n fetch: fetch ?? undefined,\n },\n () =>\n [\n `Templatical live preview running at ${handle.url}`,\n handle.fellBack\n ? `(port ${preferredPort} was busy - using ${handle.port})`\n : \"\",\n `${customBlock ? \"Custom block\" : \"Working file\"}: ${handle.workingPath}`,\n fetch ? fetchLine(fetch) : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n );\n\n if (args.flags[\"no-open\"]) {\n note(`Open ${handle.url} in a browser.`);\n } else {\n note(`Opening ${handle.url} in your default browser...`);\n openBrowser(handle.url);\n }\n note(\"After writing the working file, run: templatical live reload\");\n\n // Block: the bridge owns the process until a signal arrives.\n return new Promise<number>(() => {});\n}\n","#!/usr/bin/env node\n// The CLI entry point.\n//\n// Every command is a thin shell over this package's library functions; the\n// dispatch below owns argument handling, the error→exit-code mapping, and\n// nothing else. Human-facing output goes through cli/output.ts.\n\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { parseArgs } from \"./cli/args\";\nimport { note, setJsonMode } from \"./cli/output\";\nimport {\n EXIT,\n InvalidTemplateError,\n MissingDependencyError,\n UsageError,\n} from \"./cli/io\";\nimport { runSchema } from \"./cli/commands/schema\";\nimport { runValidate } from \"./cli/commands/validate\";\nimport { runRender } from \"./cli/commands/render\";\nimport { runEdit } from \"./cli/commands/edit\";\nimport { runImport } from \"./cli/commands/import\";\nimport { runCustomBlock } from \"./cli/commands/custom-block\";\nimport { runList, runLive } from \"./cli/commands/live\";\n\nconst USAGE = `templatical <command> [options]\n\n validate <file> structural + quality lint\n schema [--out <file>] print the block JSON Schema\n render <file> [--format mjml|html] [-o <file>] render to MJML or HTML\n edit <file> --op '<json>' | --ops <file> apply operations, write the result\n import <file> [--format <fmt>] | --list-formats convert a design to Templatical JSON\n live [--file <f>] [--port <n>] [--cwd <d>] [--no-open]\n live reload | live stop\n custom-block validate <file> check a custom block definition\n custom-block render <file> [--state <s>] [--format mjml|html] [-o <file>]\n custom-block fetch <file> [--values '<json>'] run its dataSourcePreview once\n live --custom-block <file> [--host <template>] [--fetch [--values '<json>']]\n preview a custom block\n list working files in .templatical/\n\nOptions:\n --json machine-readable output on stdout\n`;\n\nexport async function main(argv: string[]): Promise<number> {\n const args = parseArgs(argv);\n setJsonMode(args.json);\n\n switch (args.command) {\n case \"validate\":\n return runValidate(args);\n case \"schema\":\n return runSchema(args);\n case \"render\":\n return await runRender(args);\n case \"edit\":\n return runEdit(args);\n case \"import\":\n return await runImport(args);\n case \"live\":\n return await runLive(args);\n case \"list\":\n return runList(args);\n case \"custom-block\":\n return await runCustomBlock(args);\n case \"help\":\n note(USAGE);\n return EXIT.ok;\n case undefined:\n // parseArgs strips leading dashes into flags, so `--help`/`-h` never\n // reach args.command — they land here, not on a (dead) case \"--help\".\n // Asking for help is not a usage error; a bare invocation still is.\n if (args.flags.help === true || args.flags.h === true) {\n note(USAGE);\n return EXIT.ok;\n }\n note(USAGE);\n return EXIT.usage;\n default:\n note(`Unknown command \"${args.command}\".\\n\\n${USAGE}`);\n return EXIT.usage;\n }\n}\n\n// Only self-invoke when run as the CLI entry point, not when imported — this\n// package's own tests import `main` directly to exercise the dispatch above\n// without spawning a subprocess.\n//\n// import.meta.url is always a realpath, but process.argv[1] is not: npm's\n// `bin` field links a real symlink into the consumer's node_modules/.bin\n// (e.g. node_modules/.bin/templatical -> ../@templatical/template-tools/dist/bin.js),\n// and Node leaves argv[1] as that symlink path instead of resolving it.\n// Comparing them directly is false for every consumer who runs the published\n// binary, so main() would silently never run. realpathSync(argv[1]) resolves\n// the symlink so both sides name the same file.\nexport function isEntryPoint(): boolean {\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return pathToFileURL(realpathSync(argv1)).href === import.meta.url;\n } catch {\n // realpathSync throws (e.g. ENOENT) if argv[1] doesn't exist or can't be\n // read. This guard runs at module load, so failing open into a crash\n // would be worse than just not self-invoking.\n return false;\n }\n}\n\nif (isEntryPoint()) {\n main(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n if (err instanceof InvalidTemplateError) {\n note(err.message);\n for (const e of err.errors) note(` - ${e}`);\n process.exit(EXIT.invalid);\n }\n if (err instanceof MissingDependencyError) {\n note(err.message);\n process.exit(EXIT.missingDep);\n }\n if (err instanceof UsageError) {\n note(err.message);\n process.exit(EXIT.usage);\n }\n note(`Unexpected error: ${(err as Error)?.message ?? String(err)}`);\n process.exit(EXIT.usage);\n });\n}\n"],"mappings":";;;;;;;;;;AAIA,MAAM,8BAAc,IAAI,IAAI;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAUD,SAAgB,UAAU,MAA4B;CACpD,MAAM,aAAuB,CAAC;CAC9B,MAAM,QAAuC,CAAC;CAC9C,IAAI,OAAO;CAEX,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,WAAW,KAAK,GAAG;GACnB;EACF;EAEA,MAAM,OAAO,IAAI,QAAQ,QAAQ,EAAE;EACnC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,OAAO,IAAI;GACb,MAAM,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,MAAM,KAAK,CAAC;GAC5C;EACF;EACA,IAAI,SAAS,QAAQ;GACnB,OAAO;GACP;EACF;EAGA,MAAM,QACJ,YAAY,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,SAAS,KAAK,EAAE,KAAK;CAC/D;CAEA,OAAO;EAAE,SAAS,WAAW,MAAM;EAAG;EAAY;EAAM;CAAM;AAChE;;AAGA,SAAgB,UACd,MACA,GAAG,OACiB;CACpB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,IAAI,KAAK,MAAM;EACrB,IAAI,OAAO,MAAM,UAAU,OAAO;CACpC;AAEF;;;AC5DA,IAAI,WAAW;AAEf,SAAgB,YAAY,IAAmB;CAC7C,WAAW;AACb;;;;;;AAWA,SAAgB,KAAK,SAAkB,OAA2B;CAChE,QAAQ,OAAO,MACb,WAAW,GAAG,KAAK,UAAU,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GACzD;AACF;;AAGA,SAAgB,KAAK,SAAuB;CAC1C,QAAQ,OAAO,MAAM,GAAG,QAAQ,GAAG;AACrC;;;AC3BA,MAAa,OAAO;CAClB,IAAI;;CAEJ,SAAS;;CAET,OAAO;;CAEP,YAAY;AACd;AAEA,IAAa,aAAb,cAAgC,MAAM,CAAC;AAEvC,IAAa,uBAAb,cAA0C,MAAM;CAGnC;CAFX,YACE,SACA,SAA4B,CAAC,GAC7B;EACA,MAAM,OAAO;EAFJ,KAAA,SAAA;CAGX;AACF;AAEA,IAAa,yBAAb,cAA4C,MAAM;CAErC;CADX,YACE,KACA,SACA;EACA,MAAM,OAAO;EAHJ,KAAA,MAAA;CAIX;AACF;AAEA,SAAgB,YAAY,MAAc,MAAM,QAAQ,IAAI,GAAW;CACrE,OAAO,WAAW,IAAI,IAAI,OAAO,QAAQ,KAAK,IAAI;AACpD;AAEA,SAAgB,iBAAiB,MAAc,MAAM,QAAQ,IAAI,GAAY;CAC3E,MAAM,OAAO,YAAY,MAAM,GAAG;CAClC,IAAI;CACJ,IAAI;EACF,MAAM,aAAa,MAAM,MAAM;CACjC,QAAQ;EACN,MAAM,IAAI,WAAW,kBAAkB,MAAM;CAC/C;CACA,IAAI;EACF,OAAO,KAAK,MAAM,GAAG;CACvB,SAAS,KAAK;EACZ,MAAM,IAAI,WACR,GAAG,KAAK,sBAAuB,IAAc,SAC/C;CACF;AACF;;AAGA,SAAgB,kBACd,MACA,SACA,MAAM,QAAQ,IAAI,GACV;CACR,MAAM,OAAO,YAAY,MAAM,GAAG;CAClC,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,cAAc,MAAM,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,KAAK,MAAM;CACnE,OAAO;AACT;;;AC7DA,SAAgB,UAAU,MAA0B;CAClD,MAAM,MAAM,UAAU,MAAM,OAAO,GAAG;CACtC,IAAI,KAAK;EAMP,KAAK,SAHQ,kBAAkB,KAAK,MAGtB,GAAM;EACpB,OAAO,KAAK;CACd;CACA,KAAK,cAAc,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;CAClD,OAAO,KAAK;AACd;;;ACbA,SAAgB,YAAY,MAA0B;CACpD,MAAM,OAAO,KAAK,WAAW;CAC7B,IAAI,CAAC,MAAM,MAAM,IAAI,WAAW,iCAAiC;CAGjE,MAAM,OAAO,iBAAiB,MAFlB,UAAU,MAAM,KAAK,KAAK,QAAQ,IAAI,CAEX;CACvC,MAAM,EAAE,OAAO,WAAW,iBAAiB,IAAI;CAE/C,IAAI,CAAC,OAAO;EACV,KAAK;GAAE,OAAO;GAAO;GAAQ,QAAQ,CAAC;EAAE,SACtC,CACE,mCAAmC,OAAO,OAAO,KACjD,GAAG,OAAO,KAAK,MAAM,OAAO,GAAG,CACjC,CAAC,CAAC,KAAK,IAAI,CACb;EACA,OAAO,KAAK;CACd;CAGA,MAAM,UAAU,eAAe,IAAI;CACnC,IAAI,QAAQ,OACV,KAAK,+BAA+B,QAAQ,OAAO;CAGrD,MAAM,SAAS,QAAQ;CACvB,MAAM,WAAW,OAAO,QAAQ,MAAM,EAAE,aAAa,OAAO;CAE5D,KAAK;EAAE,OAAO;EAAM,QAAQ,CAAC;EAAG;CAAO,SAAS;EAC9C,IAAI,OAAO,WAAW,GAAG,OAAO;EAChC,MAAM,QAAQ,OAAO,KAClB,MAAM,QAAQ,EAAE,SAAS,IAAI,EAAE,OAAO,IAAI,EAAE,SAC/C;EACA,OAAO,CACL,0BAA0B,OAAO,OAAO,qBACxC,GAAG,KACL,CAAC,CAAC,KAAK,IAAI;CACb,CAAC;CAID,OAAO,SAAS,SAAS,IAAI,KAAK,UAAU,KAAK;AACnD;;;;;;;;;;;;AC0BA,SAAS,eAAe,WAAmB,KAA4B;CACrE,IAAI,UAAU,QAAQ,GAAG;CACzB,SAAS;EACP,MAAM,YAAY,KAAK,SAAS,gBAAgB,SAAS;EACzD,IAAI,WAAW,KAAK,WAAW,cAAc,CAAC,GAAG,OAAO;EACxD,MAAM,SAAS,QAAQ,OAAO;EAC9B,IAAI,WAAW,SAAS,OAAO;EAC/B,UAAU;CACZ;AACF;;;;;;;AAQA,SAAS,aAAa,QAAwB;CAC5C,MAAM,WAAW,KAAK,MACpB,aAAa,KAAK,QAAQ,cAAc,GAAG,MAAM,CACnD;CAEA,MAAM,MACJ,SAAS,WAAW,OAAO,SAAS,YAAY,WAC5C,SAAS,QAAQ,OACjB,KAAA;CACN,MAAM,aAAa,OAAO,OAAO,QAAQ,WAAW,MAAM,KAAA;CAG1D,MAAM,cACJ,OAAO,SAAS,YAAY,WAAW,SAAS,UAAU,KAAA;CAE5D,MAAM,QACJ,YAAY,WACX,OAAO,QAAQ,WAAW,MAAM,KAAA,MACjC,YAAY,WACZ,eACA,SAAS,UACT,SAAS;CAOX,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,MAAM,IAAI,MACR,GAAG,OAAO,sDACZ;CAEF,OAAO,KAAK,QAAQ,KAAK;AAC3B;AAEA,eAAsB,gBACpB,WACA,MAAc,QAAQ,IAAI,GACP;CACnB,MAAM,UAAU,CAAC,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,CAAC;CAE7D,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,SAAS,eAAe,WAAW,MAAM;EAC/C,IAAI,CAAC,QAAQ;EAGb,MAAM,WAAW,aAAa,MAAM;EACpC,IAAI;GACF,OAAQ,MAAM,OAAO,cAAc,QAAQ,CAAC,CAAC;EAC/C,QAAQ;GAMN;EACF;CACF;CACA,OAAO;AACT;;;ACtIA,eAAsB,SAAS,KAAwC;CACrE,MAAM,MAAM,MAAM,gBAAwC,QAAQ,GAAG;CACrE,OAAO,MAAM,IAAI,UAAU;AAC7B;AAEA,eAAsB,YACpB,KACA,SACoB;CACpB,MAAM,OAAO,MAAM,SAAS,GAAG;CAC/B,IAAI,CAAC,MACH,MAAM,IAAI,uBACR,QACA,GAAG,QAAQ,iFACb;CAEF,OAAO;AACT;;;AChBA,MAAMA,4BAAU,IAAI,IAAI,CAAC,QAAQ,MAAM,CAAC;AAExC,eAAsB,UAAU,MAAmC;CACjE,MAAM,OAAO,KAAK,WAAW;CAC7B,IAAI,CAAC,MAAM,MAAM,IAAI,WAAW,+BAA+B;CAE/D,MAAM,SAAS,UAAU,MAAM,QAAQ,KAAK;CAC5C,IAAI,CAACA,UAAQ,IAAI,MAAM,GACrB,MAAM,IAAI,WAAW,qBAAqB,OAAO,qBAAqB;CAGxE,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,IAAI;CAClD,MAAM,OAAO,iBAAiB,MAAM,GAAG;CAKvC,MAAM,EAAE,OAAO,WAAW,iBAAiB,IAAI;CAC/C,IAAI,CAAC,OACH,MAAM,IAAI,qBACR,GAAG,KAAK,8BAA8B,OAAO,OAAO,cACpD,MACF;CAIF,MAAM,OAAO,MAAM,aAAaC,IAAO;CAEvC,IAAI,SAAS;CACb,IAAI,WAAW,QAEb,UAAU,OAAM,MADM,YAAY,KAAK,gBAAgB,EAAA,CAC/B,MAAM,EAAE,iBAAiB,OAAO,CAAC,EAAA,CAAG;CAG9D,MAAM,MAAM,UAAU,MAAM,OAAO,GAAG;CACtC,IAAI,KAAK;EACP,MAAM,OAAO,YAAY,KAAK,GAAG;EAGjC,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,MAAM,QAAQ,MAAM;EAClC,KAAK,SAAS,MAAM;EACpB,OAAO,KAAK;CACd;CAEA,KAAK;EAAE;EAAQ;CAAO,SAAS,MAAM;CACrC,OAAO,KAAK;AACd;;;AC1CA,SAAS,gBAAgB,MAAkB,KAA+B;CACxE,MAAM,SAAS,UAAU,MAAM,IAAI;CACnC,MAAM,YAAY,UAAU,MAAM,KAAK;CAIvC,IAAI,WAAW,KAAA,KAAa,cAAc,KAAA,GACxC,MAAM,IAAI,WAAW,sCAAsC;CAE7D,IAAI,QACF,IAAI;EACF,OAAO,CAAC,KAAK,MAAM,MAAM,CAAmB;CAC9C,SAAS,KAAK;EACZ,MAAM,IAAI,WAAW,2BAA4B,IAAc,SAAS;CAC1E;CAEF,IAAI,WAAW;EACb,MAAM,SAAS,iBAAiB,WAAW,GAAG;EAC9C,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,MAAM,IAAI,WAAW,iDAAiD;EAExE,OAAO;CACT;CACA,MAAM,IAAI,WACR,yEACF;AACF;AAEA,SAAgB,QAAQ,MAA0B;CAChD,MAAM,OAAO,KAAK,WAAW;CAC7B,IAAI,CAAC,MAAM,MAAM,IAAI,WAAW,6BAA6B;CAC7D,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,IAAI;CAElD,MAAM,aAAa,gBAAgB,MAAM,GAAG;CAC5C,IAAI,UAAU,iBAAiB,MAAM,GAAG;CAKxC,KAAK,MAAM,CAAC,OAAO,UAAU,WAAW,QAAQ,GAAG;EACjD,MAAM,SAAS,eAAe,SAAS;GACrC,WAAW,MAAM;GACjB,MAAM,MAAM,QAAQ,CAAC;GACrB,WAAW,KAAK,IAAI;EACtB,CAAC;EACD,IAAI,CAAC,OAAO,IACV,MAAM,IAAI,WACR,aAAa,QAAQ,EAAE,IAAI,OAAO,MAAM,SAAS,EAAE,kBAAkB,OAAO,OAC9E;EAEF,UAAU,OAAO;CACnB;CAIA,MAAM,EAAE,OAAO,WAAW,iBAAiB,OAAO;CAClD,IAAI,CAAC,OACH,MAAM,IAAI,qBACR,kDAAkD,OAAO,OAAO,cAAc,KAAK,oBACnF,MACF;CAGF,MAAM,OAAO,kBAAkB,MAAM,SAAS,GAAG;CACjD,KACE;EAAE,SAAS,WAAW;EAAQ,MAAM;CAAK,SACnC,WAAW,WAAW,OAAO,mBAAmB,MACxD;CACA,OAAO,KAAK;AACd;;;AC3DA,MAAa,UAAsC;CACjD,SAAS;EACP,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,SAAS;EACP,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,QAAQ;EACN,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,OAAO;EACL,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,YAAY;EACV,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,kBAAkB;EAChB,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,MAAM;EACJ,KAAK;EACL,IAAI;EACJ,OAAO;CACT;CACA,MAAM;EACJ,KAAK;EACL,IAAI;EACJ,OAAO;CACT;AACF;;AAGA,SAAS,gBAAgB,MAAc,KAAqB;CAC1D,MAAM,OAAO,IAAI;CACjB,MAAM,QAAQ,KAAK;CACnB,MAAM,QAAQ,KAAK,YAAY;CAC/B,IAAI,MAAM;CACV,IAAI,MAAM;CACV,OAAO,MAAM,KAAK,QAAQ;EACxB,MAAM,QAAQ,MAAM,QAAQ,MAAM,GAAG;EACrC,IAAI,UAAU,IAAI;GAChB,OAAO,KAAK,MAAM,GAAG;GACrB;EACF;EACA,MAAM,OAAO,MAAM,QAAQ,KAAK;EAEhC,IAAI,SAAS,KAAA,KAAa,YAAY,KAAK,IAAI,GAAG;GAChD,OAAO,KAAK,MAAM,KAAK,QAAQ,KAAK,MAAM;GAC1C,MAAM,QAAQ,KAAK;GACnB;EACF;EACA,OAAO,KAAK,MAAM,KAAK,KAAK;EAC5B,MAAM,KAAK,KAAK,QAAQ,KAAK,KAAK;EAClC,IAAI,OAAO,IAAI;EACf,MAAM,UAAU,MAAM,QAAQ,OAAO,KAAK,CAAC;EAC3C,IAAI,YAAY,IAAI;EACpB,MAAM,UAAU,KAAK,QAAQ,KAAK,OAAO;EACzC,IAAI,YAAY,IAAI;EACpB,OAAO;EACP,MAAM,UAAU;CAClB;CACA,OAAO;AACT;;AAGA,SAAS,kBAAkB,MAAwB;CACjD,MAAM,WAAW,gBAAgB,gBAAgB,MAAM,OAAO,GAAG,QAAQ;CACzE,MAAM,SAAmB,CAAC;CAC1B,MAAM,KAAK;CACX,IAAI;CACJ,OAAQ,IAAI,GAAG,KAAK,QAAQ,GAC1B,OAAO,KAAK,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC;CAEzD,OAAO;AACT;;AAGA,SAAS,oBAAoB,MAAwB;CACnD,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG,OAAO;CACjE,MAAM,SAAS,kBAAkB,IAAI;CACrC,IACE,OAAO,MACJ,MACC,MAAM,gBACN,MAAM,mBACN,MAAM,yBACN,EAAE,WAAW,YAAY,CAC7B,GAEA,OAAO;CAET,OAAO,OAAO,MACX,MACC,MAAM,gBAAgB,MAAM,qBAAqB,MAAM,gBAC3D;AACF;;AAGA,SAAS,gBAAgB,MAAwB;CAC/C,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,IAAI;CACV,IAAI,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,WAAW,WAAW,GAAG,OAAO;CACzE,IAAI,MAAM,QAAQ,EAAE,QAAQ,GAAG,OAAO,EAAE,SAAS,KAAK,eAAe;CACrE,OAAO;AACT;;AAGA,SAAgB,aAAa,UAAkB,SAAgC;CAC7E,MAAM,MAAM,QAAQ,QAAQ,CAAC,CAAC,YAAY;CAC1C,MAAM,UAAU,QAAQ,UAAU;CAKlC,IAAI,QAAQ,SAAS,OAAO;CAC5B,IAAI,uCAAuC,KAAK,OAAO,GAAG,OAAO;CACjE,IAAI,qBAAqB,KAAK,OAAO,GAAG,OAAO;CAK/C,IAAI,oBAAoB,OAAO,GAAG,OAAO;CAEzC,IAAI,QAAQ,WAAW,QAAQ,QAAQ,OAAO;CAC9C,IAAI,QAAQ,WAAW,GAAG,GAAG,OAAO;CACpC,IAAI,QAAQ,WAAW,GAAG,GAAG;EAC3B,IAAI;EAQJ,IAAI;GACF,MAAM,KAAK,MAAM,OAAO;EAC1B,QAAQ;GACN,OAAO;EACT;EAEA,IAAI,KAAK,MAAM,MAAM,OAAO;EAE5B,IAAI,KAAK,MAAM,MAAM,OAAO;EAE5B,IAAI,KAAK,YAAY,mBAAmB,OAAO;EAG/C,IAAI,KAAK,MAAM,SAAS,QAAQ,OAAO;EAGvC,MAAM,OACJ,KAAK,SAAS,SAAS,SACnB,IAAI,UACJ,KAAK,SAAS,SACZ,MACA;EACR,IAAI,QAAQ,gBAAgB,IAAI,GAAG,OAAO;EAE1C,IAAI,OAAO,KAAK,SAAS,YAAY,oBAAoB,IAAI,IAAI,GAC/D,OAAO;EAET,OAAO;CACT;CACA,OAAO;AACT;;AAGA,SAAgB,mBAAmB,QAGjC;CAEA,IADgB,OAAO,UACb,CAAC,CAAC,WAAW,GAAG,GACxB,IAAI;EACF,MAAM,MAAM,KAAK,MAAM,MAAM;EAC7B,IAAI,OAAO,KAAK,SAAS,UACvB,OAAO;GACL,MAAM,IAAI;GACV,KAAK,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM,KAAA;EAC/C;CAEJ,QAAQ,CAER;CAEF,OAAO,EAAE,MAAM,OAAO;AACxB;;AAGA,SAAS,WAAW,YAAoB,QAAoC;CAE1E,IAAI,OAAO,UAAU,CAAC,CAAC,WAAW,GAAG,GAAG,OAAO,KAAA;CAC/C,MAAM,UAAU,WAAW,QAAQ,YAAY,MAAM;CACrD,IAAI,YAAY,cAAc,CAAC,WAAW,OAAO,GAAG,OAAO,KAAA;CAC3D,OAAO,aAAa,SAAS,MAAM;AACrC;;;;;;AAgBA,SAAgB,gBAAgB,QAA+B;CAC7D,MAAM,IAAI;CAEV,MAAM,SAAuB;EAC3B,OAAO;EACP,WAAW;EACX,cAAc;EACd,cAAc;EACd,SAAS;EACT,UAAU,GAAG,YAAY,CAAC;CAC5B;CACA,KAAK,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG;EAChC,OAAO;EACP,IAAI,EAAE,WAAW,aAAa,OAAO;OAChC,IAAI,EAAE,WAAW,gBAAgB,OAAO;OACxC,IAAI,EAAE,WAAW,iBAAiB,OAAO;OACzC,IAAI,EAAE,WAAW,WAAW,OAAO;CAC1C;CACA,OAAO;AACT;AAEA,eAAe,YAAY,KAA8B;CACvD,MAAM,UAID,CAAC;CACN,KAAK,MAAM,CAAC,QAAQ,SAAS,OAAO,QAAQ,OAAO,GACjD,QAAQ,KAAK;EACX;EACA,SAAS,KAAK;EACd,WAAY,MAAM,gBAAgB,KAAK,KAAK,GAAG,MAAO;CACxD,CAAC;CAEH,KAAK,EAAE,QAAQ,SACb,QACG,KACE,MACC,KAAK,EAAE,OAAO,OAAO,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,YAAY,KAAK,qBAC/D,CAAC,CACA,KAAK,IAAI,CACd;CACA,OAAO,KAAK;AACd;AAEA,eAAsB,UAAU,MAAmC;CACjE,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,IAAI;CAClD,IAAI,KAAK,MAAM,iBAAiB,OAAO,YAAY,GAAG;CAEtD,MAAM,OAAO,KAAK,WAAW;CAC7B,IAAI,CAAC,MAAM,MAAM,IAAI,WAAW,6BAA6B;CAC7D,MAAM,OAAO,YAAY,MAAM,GAAG;CAElC,IAAI;CACJ,IAAI;EACF,SAAS,aAAa,MAAM,MAAM;CACpC,QAAQ;EACN,MAAM,IAAI,WAAW,kBAAkB,MAAM;CAC/C;CAEA,MAAM,QAAQ,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,IAAI;CAC5C,MAAM,YAAY,UAAU,MAAM,QAAQ;CAC1C,IAAI,aAAa,CAAC,QAAQ,YACxB,MAAM,IAAI,WACR,qBAAqB,UAAU,oBAAoB,MAAM,EAC3D;CAEF,MAAM,SAAS,aAAa,aAAa,MAAM,MAAM;CACrD,IAAI,CAAC,QACH,MAAM,IAAI,WACR,kCAAkC,KAAK,+BAA+B,MAAM,EAC9E;CAGF,MAAM,OAAO,QAAQ;CACrB,MAAM,MAAM,MAAM,gBAAyC,KAAK,KAAK,GAAG;CACxE,IAAI,CAAC,KACH,MAAM,IAAI,uBACR,KAAK,KACL,aAAa,OAAO,SAAS,KAAK,IAAI,0CAA0C,KAAK,KACvF;CAEF,MAAM,UAAU,IAAI,KAAK;CAQzB,IAAI;CACJ,IAAI;CACJ,IAAI,KAAK,UAAU,UAAU;EAK3B,MAAM,WAAW,mBAAmB,MAAM;EAC1C,MAAM,MAAM,SAAS,OAAO,WAAW,MAAM,MAAM;EACnD,CAAC,CAAE,SAAS,UAAW,QAAQ,SAAS,MAAM,MAAM,EAAE,IAAI,IAAI,KAAA,CAAS;CACzE,OAAO;EACL,MAAM,QAAQ,KAAK,UAAU,SAAS,KAAK,MAAM,MAAM,IAAI;EAC3D,CAAC,CAAE,SAAS,UAAW,QAAQ,KAAK;CACtC;CAEA,MAAM,UAAU,UAAU,MAAM,KAAK,KAAK,SAAS,MAAM,QAAQ,IAAI,CAAC;CACtE,MAAM,UAAU,kBACd,GAAG,YAAY,GAAG,QAAQ,QAC1B,SACA,GACF;CAEA,MAAM,SAAS,gBAAgB,MAAM;CACrC,MAAM,QAAQ,OAAO,eAAe,OAAO,UAAU;CACrD,KAAK;EAAE;EAAQ,MAAM;EAAS,QAAQ;CAAO,SAC3C;EACE,YAAY,OAAO,MAAM;EACzB,KAAK,OAAO,UAAU,cAAc,OAAO,aAAa,iBAAiB,OAAO,aAAa,kBAAkB,OAAO,QAAQ;EAC9H,GAAG,OAAO,SAAS,KAAK,MAAM,OAAO,GAAG;EACxC,QACI,4EACA;CACN,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IAAI,CACd;CACA,OAAO,KAAK;AACd;;;ACnWA,MAAMC,UACJ;AAEF,SAAS,aAAa,QAAoC;CACxD,OAAO,OACJ,KACE,MACC,QAAQ,EAAE,SAAS,IAAI,EAAE,SAAS,EAAE,OAAO,IAAI,EAAE,SAAS,GAAG,IAAI,EAAE,SACvE,CAAC,CACA,KAAK,IAAI;AACd;;AAGA,SAAgB,gBACd,KACyB;CACzB,IAAI,QAAQ,KAAA,GAAW,OAAO,CAAC;CAC/B,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN,MAAM,IAAI,WAAW,iCAAiC;CACxD;CACA,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GACvE,MAAM,IAAI,WAAW,iCAAiC;CAExD,OAAO;AACT;AAEA,SAAS,UAAU,MAAc,KAAqC;CACpE,MAAM,OAAO,iBAAiB,MAAM,GAAG;CACvC,MAAM,IAAI,8BAA8B,IAAI;CAC5C,IAAI,CAAC,EAAE,OACL,MAAM,IAAI,qBACR,GAAG,KAAK,2CACR,EAAE,OAAO,QAAQ,MAAM,EAAE,aAAa,OAAO,CAAC,CAAC,KAAK,MAAM,EAAE,OAAO,CACrE;CAEF,OAAO;AACT;AAEA,eAAsB,eAAe,MAAmC;CACtE,MAAM,MAAM,KAAK,WAAW;CAC5B,MAAM,OAAO,KAAK,WAAW;CAC7B,IAAI,QAAQ,cAAc,QAAQ,YAAY,QAAQ,SACpD,MAAM,IAAI,WAAW,yBAAyB,OAAO,GAAG,KAAKA,SAAO;CAEtE,IAAI,CAAC,MACH,MAAM,IAAI,WAAW,gBAAgB,IAAI,0BAA0B;CACrE,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,IAAI;CAElD,IAAI,QAAQ,YAAY;EACtB,MAAM,OAAO,MAAM,SAAS,GAAG;EAC/B,MAAM,SAAS,MAAM,iBAAiB,iBAAiB,MAAM,GAAG,GAAG,EACjE,aAAa,OACT,OAAO,OAAO,EACZ,SAAS,MAAM,KAAK,GAAG,EAAE,iBAAiB,OAAO,CAAC,EAAA,CAAG,OACvD,KACA,KAAA,EACN,CAAC;EACD,IAAI,CAAC,MACH,KAAK,gEAAgE;EACvE,KAAK,cACH,OAAO,OAAO,WAAW,IACrB,qCACA,GAAG,OAAO,QAAQ,YAAY,YAAY,KAAK,OAAO,OAAO,OAAO,cAAc,aAAa,OAAO,MAAM,GAClH;EACA,OAAO,OAAO,QAAQ,KAAK,KAAK,KAAK;CACvC;CAEA,MAAM,MAAM,UAAU,MAAM,GAAG;CAE/B,IAAI,QAAQ,UAAU;EACpB,MAAM,QAAQ,UAAU,MAAM,OAAO;EACrC,IACE,UAAU,KAAA,KACV,CAAE,gBAAsC,SAAS,KAAK,GAEtD,MAAM,IAAI,WACR,oBAAoB,MAAM,iBAAiB,gBAAgB,KAAK,IAAI,EAAE,EACxE;EAIF,IAAI,UAAU,KAAA,GAAW;GACvB,MAAM,SAAS,cAAc,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK;GACpD,IAAI,CAAE,OAAoB,SAAS,KAAK,GACtC,MAAM,IAAI,WACR,GAAG,KAAK,WAAW,MAAM,qBAAqB,UAAU,YAAY,YAAY,aAAa,sBAAsB,OAAO,KAAK,IAAI,EAAE,EACvI;EAEJ;EACA,MAAM,SAAS,UAAU,MAAM,QAAQ,KAAK;EAC5C,IAAI,WAAW,UAAU,WAAW,QAClC,MAAM,IAAI,WAAW,qBAAqB,OAAO,qBAAqB;EACxE,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,mBACb,KACA,sBACE,KACA,KAAA,GACA,KACF,CACF;EACF,SAAS,KAAK;GACZ,MAAM,IAAI,qBAAqB,GAAG,KAAK,oBAAoB,CACxD,IAAc,OACjB,CAAC;EACH;EACA,IAAI,WAAW,QACb,UACE,OACE,MAAM,YAAY,KAAK,gBAAgB,EAAA,CACvC,QAAQ,EAAE,iBAAiB,OAAO,CAAC,EAAA,CACrC;EACJ,MAAM,MAAM,UAAU,MAAM,OAAO,GAAG;EACtC,IAAI,KAAK;GACP,MAAM,OAAO,YAAY,KAAK,GAAG;GACjC,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;GAC5C,cAAc,MAAM,QAAQ,MAAM;GAClC,KAAK,SAAS,MAAM;GACpB,OAAO,KAAK;EACd;EACA,KAAK;GAAE;GAAQ;EAAO,SAAS,MAAM;EACrC,OAAO,KAAK;CACd;CAGA,IAAI,CAAC,IAAI,mBACP,MAAM,IAAI,WAAW,GAAG,KAAK,sCAAsC;CACrE,MAAM,eAAe,YAAY,GAAG,CAAC,CAAC,QAAQ,MAAM,EAAE,aAAa,OAAO;CAC1E,IAAI,aAAa,SAAS,GACxB,MAAM,IAAI,qBACR,GAAG,KAAK,2DACR,aAAa,KAAK,MAAM,GAAG,EAAE,OAAO,IAAI,EAAE,SAAS,CACrD;CAEF,MAAM,YAAY,gBAAgB,UAAU,MAAM,QAAQ,CAAC;CAC3D,MAAM,WAAW,cAAc,GAAG,CAAC,CAAC,EAAE,CAAC;CACvC,MAAM,SAAS,MAAM,UAAU,IAAI,mBAAmB;EACpD,GAAG;EACH,GAAG;CACL,CAAC;CACD,KAAK,cACH,OAAO,KACH,KAAK,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO,QAAQ,MAAM,CAAC,IAAI,OAAO,SAAS,SAAS,eAAe,OAAO,SAAS,KAAK,IAAI,MAAM,OACxI,KAAK,OAAO,OAClB;CACA,OAAO,OAAO,KAAK,KAAK,KAAK,KAAK;AACpC;;;;;;;;;;;;;ACjJA,SAAS,UAAU,SAAiC;CAClD,OAAO,UAAW,SAAkC,MAAM;AAC5D;AAEA,SAAS,UAAU,QAAgC;CACjD,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO;CACnC,KAAK,MAAM,SAAS,QAIhB;EACF,IAAI,OAAO,SAAS,WAAW,OAAO,MAAM,YAAY,UACtD,OAAO,MAAM;EAEf,IAAI,OAAO,SAAS,aAAa,MAAM,QAAQ,MAAM,QAAQ,GAC3D,KAAK,MAAM,UAAU,MAAM,UAAU;GACnC,MAAM,MAAM,UAAU,MAAM;GAC5B,IAAI,KAAK,OAAO;EAClB;CAEJ;CACA,OAAO;AACT;AAEA,SAAgB,QAAQ,MAA0B;CAChD,MAAM,MAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,IAAI;CAClD,MAAM,YAAY,iBAAiB,GAAG,CAAC,CAAC,KAAK,UAAU;EACrD;EACA,OAAO,UAAU,gBAAgB,KAAK,KAAK,aAAa,IAAI,CAAC,CAAC;CAChE,EAAE;CACF,KAAK,EAAE,UAAU,SACf,UAAU,WAAW,IACjB,mBAAmB,YAAY,KAC/B,UACG,KAAK,MAAM,KAAK,EAAE,OAAO,EAAE,QAAQ,MAAM,EAAE,UAAU,IAAI,CAAC,CAC1D,KAAK,IAAI,CAClB;CACA,OAAO,KAAK;AACd;AAEA,eAAe,OACb,MACA,MACA,MACmB;CACnB,OAAO,MAAM,oBAAoB,OAAO,QAAQ;EAC9C,QAAQ;EACR,SAAS,EAAE,gBAAgB,mBAAmB;EAC9C,MAAM,SAAS,KAAA,IAAY,KAAA,IAAY,KAAK,UAAU,IAAI;CAC5D,CAAC;AACH;;AAGA,SAAS,UAAU,OAA6B;CAC9C,IAAI,CAAC,MAAM,IAAI,OAAO,sCAAsC,MAAM;CAClE,OAAO,MAAM,SAAS,SAAS,IAC3B,4DAA4D,MAAM,SAAS,KAAK,IAAI,EAAE,KACtF;AACN;AAEA,eAAsB,QAAQ,MAAmC;CAC/D,MAAM,MAAM,KAAK,WAAW;CAC5B,MAAM,MAAM,YAAY,UAAU,MAAM,KAAK,KAAK,KAAK,QAAQ,IAAI,CAAC;CAEpE,IAAI,QAAQ,YAAY,QAAQ,QAAQ;EACtC,MAAM,OAAO,YAAY,GAAG;EAC5B,IAAI,CAAC,QAAQ,CAAC,aAAa,KAAK,GAAG,GACjC,MAAM,IAAI,WACR,sDAAsD,YAAY,mBACpE;EAEF,IAAI,QAAQ,UAAU;GACpB,MAAM,qBAAqB,KAAK,MAAM,2BAA2B;GACjE,MAAM,MAAM,MAAM,OAChB,KAAK,MACL,WACA,qBAAqB,EAAE,oBAAoB,KAAK,IAAI,KAAA,CACtD;GACA,MAAM,OAAQ,MAAM,IAAI,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;GAQ/C,MAAM,UAAU,KAAK,WAAW;GAChC,MAAM,WAAW,KAAK,aAAa;GAGnC,IAAI,IAAI,UAAU,OAAO,KAAK,OAAO,OAAO;IAM1C,MAAM,QAAQ,GAJZ,KAAK,UACJ,IAAI,UAAU,MACX,uCAAuC,IAAI,OAAO,MAClD,6CACkB;IACxB,KACE;KAAE,UAAU;KAAO,IAAI;KAAO;KAAS;KAAU;IAAM,SACjD,KACR;IACA,OAAO,KAAK;GACd;GACA,MAAM,QAAQ,KAAK;GACnB,KACE;IACE,UAAU;IACV,IAAI;IACJ;IACA;IACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC3B,SAEE,CACE,KAAK,SAAS,iBACV,8BAA8B,QAAQ,aACtC,8BAA8B,QAAQ,sBAC1C,QAAQ,UAAU,KAAK,IAAI,EAC7B,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IAAI,CAChB;GACA,OAAO,KAAK;EACd;EACA,IAAI;GACF,QAAQ,KAAK,KAAK,KAAK,SAAS;EAClC,QAAQ,CAER;EACA,OAAO,YAAY,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;EACxC,KAAK,EAAE,SAAS,KAAK,SAAS,0BAA0B;EACxD,OAAO,KAAK;CACd;CAEA,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,WACR,iBAAiB,IAAI,mDACvB;CAIF,MAAM,WAAW,UAAU,MAAM,MAAM;CACvC,MAAM,gBAAgB,WAAW,OAAO,QAAQ,IAAI;CACpD,MAAM,OAAO,UAAU,MAAM,MAAM;CACnC,MAAM,cAAc,UAAU,MAAM,cAAc;CAClD,MAAM,OAAO,UAAU,MAAM,MAAM;CACnC,IAAI,eAAe,MACjB,MAAM,IAAI,WAAW,iDAAiD;CAExE,IAAI,QAAQ,CAAC,aACX,MAAM,IAAI,WAAW,0CAA0C;CAEjE,MAAM,UAAU,KAAK,MAAM,UAAU;CACrC,IAAI,WAAW,CAAC,aACd,MAAM,IAAI,WAAW,2CAA2C;CAElE,MAAM,YAAY,UAAU,MAAM,QAAQ;CAC1C,IAAI,cAAc,KAAA,KAAa,CAAC,SAC9B,MAAM,IAAI,WAAW,qCAAqC;CAE5D,MAAM,cAAc,UAAU,gBAAgB,SAAS,IAAI,KAAA;CAC3D,MAAM,OAAO,MAAe,WAAW,CAAC,IAAI,IAAI,QAAQ,KAAK,CAAC;CAC9D,MAAM,YACkC;EACtC,MAAM,cAAc,iBAAiB;EACrC,MAAM,IAAI,eAAe,QAAQ,KAAA,gBAAkB,eAAe,CAAC;EACnE,GAAI,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,IAAI,CAAC;EAClC,GAAI,cAAc,EAAE,OAAO,YAAY,IAAI,CAAC;CAC9C;CAEA,MAAM,WAAW,YAAY,GAAG;CAChC,IAAI,YAAY,aAAa,SAAS,GAAG,GAAG;EAG1C,MAAM,OAAO,SAAS,QAAQ;EAC9B,IACE,SAAS,UAAU,QAClB,SAAS,SAAS,KAAA,KAAa,SAAS,SAAS,UAAU,QAC3D,SAAS,mBACP,SAAS,SAAS,UAAU,QAC3B,KAAK,UAAU,SAAS,KAAK,MAAM,KAAK,UAAU,UAAU,KAAK,IAErE,MAAM,IAAI,WACR,wCAAwC,SAAS,QAAQ,eAAe,IAAI,KAAK,wCACnF;EAEF,KACE;GACE,KAAK,oBAAoB,SAAS,KAAK;GACvC,KAAK,SAAS;GACd,gBAAgB;EAClB,SAEE,oCAAoC,SAAS,IAAI,wBAAwB,SAAS,KAAK,EAC3F;EACA,OAAO,KAAK;CACd;CACA,IAAI,UAAU,OAAO,YAAY,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;CAEtD,MAAM,SAAS,MAAM,sBAAsB;EACzC;EACA;EACA;EACA;EACA;EACA,GAAI,cAAc,EAAE,OAAO,EAAE,QAAQ,YAAY,EAAE,IAAI,CAAC;CAC1D,CAAC;CAED,UAAU,QAAQ,YAAY,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,cACE,YAAY,GAAG,GACf,KAAK,UAAU;EACb,KAAK,QAAQ;EACb,MAAM,OAAO;EACb,GAAG;EACH,MAAM,OAAO;CACf,CAAuB,GACvB,MACF;CAEA,MAAM,gBAAgB;EACpB,OAAO,YAAY,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;EACxC,OAAO,MAAM,CAAC,CAAC,cAAc,QAAQ,KAAK,KAAK,EAAE,CAAC;CACpD;CACA,QAAQ,GAAG,UAAU,OAAO;CAC5B,QAAQ,GAAG,WAAW,OAAO;CAI7B,MAAM,QAAQ,OAAO,UAAU;CAC/B,KACE;EACE,KAAK,OAAO;EACZ,MAAM,OAAO;EACb;EACA,UAAU,OAAO;EACjB,aAAa,OAAO;EACpB,aAAa,cAAc,OAAO,cAAc,KAAA;EAChD,OAAO,SAAS,KAAA;CAClB,SAEE;EACE,uCAAuC,OAAO;EAC9C,OAAO,WACH,SAAS,cAAc,oBAAoB,OAAO,KAAK,KACvD;EACJ,GAAG,cAAc,iBAAiB,eAAe,IAAI,OAAO;EAC5D,QAAQ,UAAU,KAAK,IAAI;CAC7B,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IAAI,CAChB;CAEA,IAAI,KAAK,MAAM,YACb,KAAK,QAAQ,OAAO,IAAI,eAAe;MAClC;EACL,KAAK,WAAW,OAAO,IAAI,4BAA4B;EACvD,YAAY,OAAO,GAAG;CACxB;CACA,KAAK,8DAA8D;CAGnE,OAAO,IAAI,cAAsB,CAAC,CAAC;AACrC;;;AC7QA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;AAoBd,eAAsB,KAAK,MAAiC;CAC1D,MAAM,OAAO,UAAU,IAAI;CAC3B,YAAY,KAAK,IAAI;CAErB,QAAQ,KAAK,SAAb;EACE,KAAK,YACH,OAAO,YAAY,IAAI;EACzB,KAAK,UACH,OAAO,UAAU,IAAI;EACvB,KAAK,UACH,OAAO,MAAM,UAAU,IAAI;EAC7B,KAAK,QACH,OAAO,QAAQ,IAAI;EACrB,KAAK,UACH,OAAO,MAAM,UAAU,IAAI;EAC7B,KAAK,QACH,OAAO,MAAM,QAAQ,IAAI;EAC3B,KAAK,QACH,OAAO,QAAQ,IAAI;EACrB,KAAK,gBACH,OAAO,MAAM,eAAe,IAAI;EAClC,KAAK;GACH,KAAK,KAAK;GACV,OAAO,KAAK;EACd,KAAK,KAAA;GAIH,IAAI,KAAK,MAAM,SAAS,QAAQ,KAAK,MAAM,MAAM,MAAM;IACrD,KAAK,KAAK;IACV,OAAO,KAAK;GACd;GACA,KAAK,KAAK;GACV,OAAO,KAAK;EACd;GACE,KAAK,oBAAoB,KAAK,QAAQ,QAAQ,OAAO;GACrD,OAAO,KAAK;CAChB;AACF;AAaA,SAAgB,eAAwB;CACtC,MAAM,QAAQ,QAAQ,KAAK;CAC3B,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI;EACF,OAAO,cAAc,aAAa,KAAK,CAAC,CAAC,CAAC,SAAS,YAAY;CACjE,QAAQ;EAIN,OAAO;CACT;AACF;AAEA,IAAI,aAAa,GACf,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CACxB,MAAM,SAAS,QAAQ,KAAK,IAAI,CAAC,CAAC,CAClC,OAAO,QAAiB;CACvB,IAAI,eAAe,sBAAsB;EACvC,KAAK,IAAI,OAAO;EAChB,KAAK,MAAM,KAAK,IAAI,QAAQ,KAAK,OAAO,GAAG;EAC3C,QAAQ,KAAK,KAAK,OAAO;CAC3B;CACA,IAAI,eAAe,wBAAwB;EACzC,KAAK,IAAI,OAAO;EAChB,QAAQ,KAAK,KAAK,UAAU;CAC9B;CACA,IAAI,eAAe,YAAY;EAC7B,KAAK,IAAI,OAAO;EAChB,QAAQ,KAAK,KAAK,KAAK;CACzB;CACA,KAAK,qBAAsB,KAAe,WAAW,OAAO,GAAG,GAAG;CAClE,QAAQ,KAAK,KAAK,KAAK;AACzB,CAAC"}
1
+ {"version":3,"file":"bin.js","names":["FORMATS","content","USAGE"],"sources":["../src/cli/args.ts","../src/cli/output.ts","../src/cli/io.ts","../src/cli/commands/schema.ts","../src/cli/commands/validate.ts","../src/cli/resolve-optional.ts","../src/cli/mjml.ts","../src/cli/commands/render.ts","../src/cli/commands/edit.ts","../src/cli/commands/import.ts","../src/cli/commands/custom-block.ts","../src/cli/commands/live.ts","../src/bin.ts"],"sourcesContent":["// Argv parsing for the CLI. Deliberately hand-rolled: the package has no\n// runtime dependency for this, and the surface is a dozen flags.\n\n/** Flags that consume the next argv entry as their value. */\nconst VALUE_FLAGS = new Set([\n \"format\",\n \"out\",\n \"o\",\n \"op\",\n \"ops\",\n \"file\",\n \"port\",\n \"cwd\",\n \"state\",\n \"values\",\n \"custom-block\",\n \"host\",\n]);\n\nexport interface ParsedArgs {\n command?: string;\n /** Everything that is not the command and not a flag — including subcommands. */\n positional: string[];\n json: boolean;\n flags: Record<string, string | true>;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const positional: string[] = [];\n const flags: Record<string, string | true> = {};\n let json = false;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (!arg.startsWith(\"-\")) {\n positional.push(arg);\n continue;\n }\n\n const bare = arg.replace(/^--?/, \"\");\n const eq = bare.indexOf(\"=\");\n if (eq !== -1) {\n flags[bare.slice(0, eq)] = bare.slice(eq + 1);\n continue;\n }\n if (bare === \"json\") {\n json = true;\n continue;\n }\n // A value flag at the end of argv has no value; record it as present so the\n // command reports a useful usage error rather than reading `undefined`.\n flags[bare] =\n VALUE_FLAGS.has(bare) && i + 1 < argv.length ? argv[++i] : true;\n }\n\n return { command: positional.shift(), positional, json, flags };\n}\n\n/** Read a value flag as a string, or undefined when absent or valueless. */\nexport function flagValue(\n args: ParsedArgs,\n ...names: string[]\n): string | undefined {\n for (const name of names) {\n const v = args.flags[name];\n if (typeof v === \"string\") return v;\n }\n return undefined;\n}\n","// The ONLY module in this package that writes to stdout.\n//\n// `--json` promises callers exactly one parseable document on stdout, and a\n// future stdio-based caller — an MCP server reserving stdout for JSON-RPC —\n// would need that to hold just as strictly. Either way a second writer\n// breaks the contract. tests/cli-output-discipline.test.ts asserts no other\n// source file touches stdout.\n\nlet jsonMode = false;\n\nexport function setJsonMode(on: boolean): void {\n jsonMode = on;\n}\n\nexport function isJsonMode(): boolean {\n return jsonMode;\n}\n\n/**\n * Emit a command's result. Under `--json` the payload is serialized; otherwise\n * `human()` is called for the readable form — as a thunk, so building the\n * pretty output costs nothing in JSON mode.\n */\nexport function emit(payload: unknown, human: () => string): void {\n process.stdout.write(\n jsonMode ? `${JSON.stringify(payload)}\\n` : `${human()}\\n`,\n );\n}\n\n/** Diagnostics, progress and errors. Always stderr, in both modes. */\nexport function note(message: string): void {\n process.stderr.write(`${message}\\n`);\n}\n","// Shared file IO and the error→exit-code contract.\n\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\n\nexport const EXIT = {\n ok: 0,\n /** Structural errors, or quality issues of severity \"error\". */\n invalid: 1,\n /** Bad flags, missing arguments, unreadable input. */\n usage: 2,\n /** An optional dependency is not installed; the message names the install. */\n missingDep: 3,\n} as const;\n\nexport class UsageError extends Error {}\n\nexport class InvalidTemplateError extends Error {\n constructor(\n message: string,\n readonly errors: string[] = [],\n ) {\n super(message);\n }\n}\n\nexport class MissingDependencyError extends Error {\n constructor(\n readonly pkg: string,\n message: string,\n ) {\n super(message);\n }\n}\n\nexport function resolveFrom(file: string, cwd = process.cwd()): string {\n return isAbsolute(file) ? file : resolve(cwd, file);\n}\n\nexport function readTemplateFile(file: string, cwd = process.cwd()): unknown {\n const path = resolveFrom(file, cwd);\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n throw new UsageError(`Could not read ${file}`);\n }\n try {\n return JSON.parse(raw);\n } catch (err) {\n throw new UsageError(\n `${file} is not valid JSON: ${(err as Error).message}`,\n );\n }\n}\n\n/** Write a template, creating the parent directory. Returns the path written. */\nexport function writeTemplateFile(\n file: string,\n content: unknown,\n cwd = process.cwd(),\n): string {\n const path = resolveFrom(file, cwd);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(content, null, 2)}\\n`, \"utf8\");\n return path;\n}\n","import { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport { EXIT, writeTemplateFile } from \"../io\";\nimport { schema } from \"../../index\";\n\nexport function runSchema(args: ParsedArgs): number {\n const out = flagValue(args, \"out\", \"o\");\n if (out) {\n // writeTemplateFile JSON-stringifies with the same shape this command\n // wrote by hand, and creates the parent directory as a side effect.\n const path = writeTemplateFile(out, schema);\n // stdout stays empty: the caller asked for a file, and under --json a path\n // string would not be the document they are parsing for.\n note(`Wrote ${path}`);\n return EXIT.ok;\n }\n emit(schema, () => JSON.stringify(schema, null, 2));\n return EXIT.ok;\n}\n","import { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport { EXIT, readTemplateFile, UsageError } from \"../io\";\nimport { runQualityLint, validateTemplate } from \"../../index\";\n\nexport function runValidate(args: ParsedArgs): number {\n const file = args.positional[0];\n if (!file) throw new UsageError(\"validate needs a template file.\");\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n\n const data = readTemplateFile(file, cwd);\n const { valid, errors } = validateTemplate(data);\n\n if (!valid) {\n emit({ valid: false, errors, issues: [] }, () =>\n [\n `✗ Structural validation failed (${errors.length}):`,\n ...errors.map((e) => ` - ${e}`),\n ].join(\"\\n\"),\n );\n return EXIT.invalid;\n }\n\n // The linter assumes a structurally-valid template, so it runs only here.\n const quality = runQualityLint(data);\n if (quality.error) {\n note(`Quality lint could not run: ${quality.error}`);\n }\n\n const issues = quality.issues;\n const blocking = issues.filter((i) => i.severity === \"error\");\n\n emit({ valid: true, errors: [], issues }, () => {\n if (issues.length === 0) return \"✓ Valid — no quality issues\";\n const lines = issues.map(\n (i) => ` - [${i.severity}] ${i.ruleId}: ${i.message}`,\n );\n return [\n `✓ Structurally valid · ${issues.length} quality issue(s):`,\n ...lines,\n ].join(\"\\n\");\n });\n\n // A severity-\"error\" issue is a real defect, not advice, so it fails the\n // command — matching the script this replaces. Warnings and info do not.\n return blocking.length > 0 ? EXIT.invalid : EXIT.ok;\n}\n","//\n// Optional dependencies must resolve from the CONSUMER'S cwd, not from this\n// module's location. When the CLI runs via npx it lives in npm's cache, so a\n// package the user installs in their own project is invisible to the CLI's own\n// resolution — and the remediation message (\"npm install mjml\") would be\n// impossible to satisfy. Anchoring the walk at the cwd fixes that; the CLI's\n// own directory is tried second, as a fallback — not for ajv (a hard\n// `dependencies` entry, imported statically in src/validate.ts, so it never\n// passes through this function at all), but for a global install (`npm\n// install -g mjml` places the optional peer as a sibling of this package\n// under one shared global node_modules that only this anchor can reach) and\n// for this package's own tests, which anchor at a throwaway cwd outside the\n// repo and rely on this anchor climbing from inside the repo tree up to the\n// root node_modules holding the @templatical/import-* converters as root\n// devDependencies.\n//\n// Resolution deliberately does NOT go through require.resolve()/createRequire.\n// That API performs Node's CJS-invoked exports resolution, which checks the\n// condition list [\"require\", \"node\", \"default\"] and never \"import\" — so it\n// throws ERR_PACKAGE_PATH_NOT_EXPORTED for any package whose exports map\n// declares only \"import\", which is every first-party package in this monorepo\n// (ESM-only: `exports` exposes only the \"import\" condition, never \"require\"\n// or \"main\"). That map also omits \"./package.json\", so\n// require.resolve can't even read the manifest to look further. The result\n// under the old createRequire-based resolution: every @templatical/import-*\n// converter reported as \"not installed\" even when built and present on disk.\n// The ESM-native alternative, import.meta.resolve(specifier, parentURL),\n// would honor the right condition set and resolve this cwd-anchored — but its\n// second (parentURL) parameter, the one needed to anchor anywhere other than\n// this module's own URL, is documented as requiring the\n// --experimental-import-meta-resolve flag, and a published CLI that must run\n// un-flagged on Node 20 and 22 cannot bet a core resolution path on that.\n//\n// The fix walks node_modules directories by hand (findPackageDir) — a plain\n// filesystem read bypasses the exports map entirely — then reads the found\n// package's own package.json and picks its entry file directly\n// (entryFileFor), honoring exports[\".\"].import, a string exports[\".\"],\n// exports[\".\"].default, a root-string exports field, \"module\", then \"main\",\n// in that order.\n//\n// Returning null instead of throwing lets callers distinguish \"not installed\"\n// (exit 3, name the install command) from \"installed but broken\" (a real error\n// worth surfacing). Same discipline as tryLoadRenderer() in\n// packages/editor/src/utils/toMjml.ts: findPackageDir returning null is the\n// only thing that continues to the next anchor — a found package's own entry\n// resolution or import() is never caught, so a genuinely broken install\n// propagates as a real error rather than a misleading install hint.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\n\ninterface ExportsConditions {\n import?: string;\n default?: string;\n}\n\ninterface PackageManifest {\n exports?: string | Record<string, string | ExportsConditions>;\n module?: string;\n main?: string;\n}\n\n/**\n * Walk up from `dir` through every ancestor's node_modules looking for\n * <specifier>/package.json — the same directory-walk Node's own resolver uses\n * to locate a package, without then asking Node to apply exports-map\n * resolution. `dir` is normalized to an absolute path via `resolve()` before\n * the walk starts, so a relative anchor (e.g. \".\") climbs real ancestors —\n * unnormalized, `dirname(\".\")` is \".\" forever and the walk would give up\n * after the first miss.\n */\nfunction findPackageDir(specifier: string, dir: string): string | null {\n let current = resolve(dir);\n for (;;) {\n const candidate = join(current, \"node_modules\", specifier);\n if (existsSync(join(candidate, \"package.json\"))) return candidate;\n const parent = dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\n/**\n * Pick a found package's entry file straight from its own manifest, honoring\n * exports[\".\"].import, a string exports[\".\"], exports[\".\"].default, a\n * root-string exports field, \"module\", then \"main\" — the fields an ESM\n * consumer needs, read without Node's exports-map gating.\n */\nfunction entryFileFor(pkgDir: string): string {\n const manifest = JSON.parse(\n readFileSync(join(pkgDir, \"package.json\"), \"utf8\"),\n ) as PackageManifest;\n\n const dot =\n manifest.exports && typeof manifest.exports === \"object\"\n ? manifest.exports[\".\"]\n : undefined;\n const conditions = dot && typeof dot === \"object\" ? dot : undefined;\n // \"exports\": \"./dist/index.js\" — no \".\" key at all. PackageManifest already\n // types this shape; the code just never read it.\n const rootExports =\n typeof manifest.exports === \"string\" ? manifest.exports : undefined;\n\n const entry =\n conditions?.import ??\n (typeof dot === \"string\" ? dot : undefined) ??\n conditions?.default ??\n rootExports ??\n manifest.module ??\n manifest.main;\n\n // typeof, not just truthiness: the tsup/tsdown types-first shape nests\n // exports[\".\"].import as a further conditions object ({ types, default }),\n // not a string. Without this check that object flows into join() below and\n // throws a raw `TypeError: The \"path\" argument must be of type string`\n // instead of this function's own, actionable error.\n if (!entry || typeof entry !== \"string\") {\n throw new Error(\n `${pkgDir} has no resolvable entry point (exports/module/main).`,\n );\n }\n return join(pkgDir, entry);\n}\n\nexport async function resolveOptional<T>(\n specifier: string,\n cwd: string = process.cwd(),\n): Promise<T | null> {\n const anchors = [cwd, dirname(fileURLToPath(import.meta.url))];\n\n for (const anchor of anchors) {\n const pkgDir = findPackageDir(specifier, anchor);\n if (!pkgDir) continue; // not found from this anchor — try the next\n // Deliberately outside the try below: a manifest that names no usable\n // entry is a packaging fault the caller can act on, and it keeps throwing.\n const resolved = entryFileFor(pkgDir);\n try {\n return (await import(pathToFileURL(resolved).href)) as T;\n } catch {\n // The manifest is fine but the file it names isn't there — a workspace\n // package whose dist/ hasn't been built is the everyday case. \"Optional\"\n // has to cover this, or `import --list-formats`, whose whole job is to\n // report which converters resolve, crashes on the first one that doesn't.\n // Keep looking: a later anchor may hold a complete copy.\n continue;\n }\n }\n return null;\n}\n","// The optional `mjml` peer, resolved from the user's cwd first. The SDK\n// bundles no MJML compiler by design, so a missing peer is either skipped\n// (validate) or a hard stop with an install hint (render --format html).\n//\n// The mjml package's top-level function is `async`, so it always returns a\n// Promise even though the underlying compile is synchronous.\n\nimport { MissingDependencyError } from \"./io\";\nimport { resolveOptional } from \"./resolve-optional\";\n\nexport type Mjml2Html = (\n mjml: string,\n options?: Record<string, unknown>,\n) => Promise<{ html: string; errors: unknown[] }>;\n\nexport async function loadMjml(cwd: string): Promise<Mjml2Html | null> {\n const mod = await resolveOptional<{ default: Mjml2Html }>(\"mjml\", cwd);\n return mod ? mod.default : null;\n}\n\nexport async function requireMjml(\n cwd: string,\n purpose: string,\n): Promise<Mjml2Html> {\n const mjml = await loadMjml(cwd);\n if (!mjml) {\n throw new MissingDependencyError(\n \"mjml\",\n `${purpose} needs the optional \\`mjml\\` package, which isn't installed.\\n npm install mjml`,\n );\n }\n return mjml;\n}\n","import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { renderToMjml } from \"@templatical/renderer\";\nimport type { TemplateContent } from \"@templatical/types\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport {\n EXIT,\n InvalidTemplateError,\n readTemplateFile,\n resolveFrom,\n UsageError,\n} from \"../io\";\nimport { requireMjml } from \"../mjml\";\nimport { validateTemplate } from \"../../index\";\n\nconst FORMATS = new Set([\"mjml\", \"html\"]);\n\nexport async function runRender(args: ParsedArgs): Promise<number> {\n const file = args.positional[0];\n if (!file) throw new UsageError(\"render needs a template file.\");\n\n const format = flagValue(args, \"format\") ?? \"mjml\";\n if (!FORMATS.has(format)) {\n throw new UsageError(`Unknown --format \"${format}\". Use mjml or html.`);\n }\n\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n const data = readTemplateFile(file, cwd);\n\n // A structurally invalid template must fail with the error list (exit 1),\n // not surface as a renderer-internals crash (exit 2) — edit.ts validates\n // for the same reason before it writes.\n const { valid, errors } = validateTemplate(data);\n if (!valid) {\n throw new InvalidTemplateError(\n `${file} is not structurally valid (${errors.length} error(s)).`,\n errors,\n );\n }\n\n const content = data as TemplateContent;\n const mjml = await renderToMjml(content);\n\n let output = mjml;\n if (format === \"html\") {\n const compile = await requireMjml(cwd, \"Rendering HTML\");\n output = (await compile(mjml, { validationLevel: \"soft\" })).html;\n }\n\n const out = flagValue(args, \"out\", \"o\");\n if (out) {\n const path = resolveFrom(out, cwd);\n // render writes raw text, not JSON, so it can't reuse writeTemplateFile —\n // create the parent directory directly instead.\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, output, \"utf8\");\n note(`Wrote ${path}`);\n return EXIT.ok;\n }\n\n emit({ format, output }, () => output);\n return EXIT.ok;\n}\n","import type { Operation, TemplateContent } from \"@templatical/types\";\nimport { applyOperations, OperationError } from \"@templatical/types\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit } from \"../output\";\nimport {\n EXIT,\n InvalidTemplateError,\n readTemplateFile,\n UsageError,\n writeTemplateFile,\n} from \"../io\";\nimport { validateTemplate } from \"../../index\";\n\nfunction parseOperations(args: ParsedArgs, cwd: string): unknown[] {\n const inline = flagValue(args, \"op\");\n const batchFile = flagValue(args, \"ops\");\n // !== undefined, not truthy: flagValue returns \"\" for an explicitly-empty\n // flag, and a truthy check would let --op \"\" --ops <file> silently run the\n // batch instead of reporting that both were passed.\n if (inline !== undefined && batchFile !== undefined) {\n throw new UsageError(\"Pass either --op or --ops, not both.\");\n }\n if (inline) {\n try {\n return [JSON.parse(inline) as unknown];\n } catch (err) {\n throw new UsageError(`--op is not valid JSON: ${(err as Error).message}`);\n }\n }\n if (batchFile) {\n const parsed = readTemplateFile(batchFile, cwd);\n if (!Array.isArray(parsed)) {\n throw new UsageError(\"--ops must point at a JSON array of operations.\");\n }\n return parsed;\n }\n throw new UsageError(\n \"edit needs --op '<json>' for one operation or --ops <file> for a batch.\",\n );\n}\n\n/** An object written in the old vocabulary: `{\"operation\": …, \"data\": …}`. */\nfunction isOldVocabulary(input: unknown): boolean {\n return (\n typeof input === \"object\" &&\n input !== null &&\n \"operation\" in input &&\n !(\"op\" in input)\n );\n}\n\nexport function runEdit(args: ParsedArgs): number {\n const file = args.positional[0];\n if (!file) throw new UsageError(\"edit needs a template file.\");\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n\n const operations = parseOperations(args, cwd);\n const oldIndex = operations.findIndex(isOldVocabulary);\n if (oldIndex !== -1) {\n throw new UsageError(\n `Operation ${oldIndex + 1} uses the old {\"operation\", \"data\"} form. ` +\n 'Write {\"op\": \"updateBlock\", \"blockId\": …, \"set\": {…}} and the like: ' +\n \"see the operations reference.\",\n );\n }\n const before = readTemplateFile(file, cwd) as TemplateContent;\n\n // All or nothing: the engine throws at the first refused operation and\n // nothing is written. A working file has one writer, so there are no locks.\n let content: TemplateContent;\n try {\n content = applyOperations(before, operations as Operation[]);\n } catch (error) {\n if (!(error instanceof OperationError)) throw error;\n const refused = operations[error.opIndex] as { op?: unknown } | undefined;\n throw new UsageError(\n `Operation ${error.opIndex + 1} (${String(refused?.op)}) was refused: ${error.code} — ${error.message}`,\n );\n }\n\n // An operation can produce a structurally invalid document, so validate\n // before writing: the working file must always be valid.\n const { valid, errors } = validateTemplate(content);\n if (!valid) {\n throw new InvalidTemplateError(\n `The edited template is not structurally valid (${errors.length} error(s)); ${file} was not written.`,\n errors,\n );\n }\n\n const path = writeTemplateFile(file, content, cwd);\n emit(\n { applied: operations.length, file: path },\n () => `Applied ${operations.length} operation(s) to ${path}`,\n );\n return EXIT.ok;\n}\n","// The format registry. A new @templatical/import-* converter is ONE entry here\n// and nothing else: reference/import.md documents the routing rule rather than\n// the list, and `--list-formats` answers what is resolvable at runtime. That is\n// what lets a new importer ship without a skill edit.\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { basename, extname } from \"node:path\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit } from \"../output\";\nimport {\n EXIT,\n MissingDependencyError,\n resolveFrom,\n UsageError,\n writeTemplateFile,\n} from \"../io\";\nimport { resolveOptional } from \"../resolve-optional\";\nimport { WORKING_DIR } from \"../../live/index\";\n\ninterface FormatSpec {\n pkg: string;\n /** The converter's exported function name. */\n fn: string;\n /**\n * What the converter is handed. `json` parses the source first, `text` passes\n * it through, and `stripo` unpacks a `{ html, css }` envelope — Stripo is the\n * one format whose export may arrive as either shape.\n */\n input: \"json\" | \"text\" | \"stripo\";\n}\n\nexport const FORMATS: Record<string, FormatSpec> = {\n unlayer: {\n pkg: \"@templatical/import-unlayer\",\n fn: \"convertUnlayerTemplate\",\n input: \"json\",\n },\n beefree: {\n pkg: \"@templatical/import-beefree\",\n fn: \"convertBeeFreeTemplate\",\n input: \"json\",\n },\n stripo: {\n pkg: \"@templatical/import-stripo\",\n fn: \"convertStripoTemplate\",\n input: \"stripo\",\n },\n topol: {\n pkg: \"@templatical/import-topol\",\n fn: \"convertTopolTemplate\",\n input: \"json\",\n },\n chamaileon: {\n pkg: \"@templatical/import-chamaileon\",\n fn: \"convertChamaileonTemplate\",\n input: \"json\",\n },\n \"easy-email-pro\": {\n pkg: \"@templatical/import-easy-email-pro\",\n fn: \"convertEasyEmailProTemplate\",\n input: \"json\",\n },\n mjml: {\n pkg: \"@templatical/import-mjml\",\n fn: \"convertMjmlTemplate\",\n input: \"text\",\n },\n html: {\n pkg: \"@templatical/import-html\",\n fn: \"convertHtmlTemplate\",\n input: \"text\",\n },\n};\n\n/** Strip a tag and its contents, so class scanning never reads CSS or script. */\nfunction withoutElements(html: string, tag: string): string {\n const open = `<${tag}`;\n const close = `</${tag}`;\n const lower = html.toLowerCase();\n let out = \"\";\n let pos = 0;\n while (pos < html.length) {\n const start = lower.indexOf(open, pos);\n if (start === -1) {\n out += html.slice(pos);\n break;\n }\n const next = lower[start + open.length];\n // `<style` matches `<styles>` too unless the next char ends the tag name.\n if (next !== undefined && /[a-z0-9-]/.test(next)) {\n out += html.slice(pos, start + open.length);\n pos = start + open.length;\n continue;\n }\n out += html.slice(pos, start);\n const gt = html.indexOf(\">\", start);\n if (gt === -1) break;\n const closeAt = lower.indexOf(close, gt + 1);\n if (closeAt === -1) break;\n const closeGt = html.indexOf(\">\", closeAt);\n if (closeGt === -1) break;\n out += \" \";\n pos = closeGt + 1;\n }\n return out;\n}\n\n/** Every class token in the markup, ignoring <style> and <script> contents. */\nfunction markupClassTokens(html: string): string[] {\n const stripped = withoutElements(withoutElements(html, \"style\"), \"script\");\n const tokens: string[] = [];\n const re = /\\bclass\\s*=\\s*([\"'])([^\"']*)\\1/gi;\n let m: RegExpExecArray | null;\n while ((m = re.exec(stripped))) {\n tokens.push(...m[2].trim().split(/\\s+/).filter(Boolean));\n }\n return tokens;\n}\n\n/** Stripo's own class prefixes, which survive both of its export shapes. */\nfunction looksLikeStripoHtml(html: unknown): boolean {\n if (typeof html !== \"string\" || html.trim().length === 0) return false;\n const tokens = markupClassTokens(html);\n if (\n tokens.some(\n (t) =>\n t === \"esd-stripe\" ||\n t === \"esd-structure\" ||\n t === \"esd-container-frame\" ||\n t.startsWith(\"esd-block-\"),\n )\n ) {\n return true;\n }\n return tokens.some(\n (t) =>\n t === \"es-wrapper\" || t === \"es-content-body\" || t === \"es-header-body\",\n );\n}\n\n/** Easy Email Pro marks its own nodes `standard-*`; OSS Easy Email does not. */\nfunction hasStandardType(node: unknown): boolean {\n if (!node || typeof node !== \"object\") return false;\n const n = node as { type?: unknown; children?: unknown };\n if (typeof n.type === \"string\" && n.type.startsWith(\"standard-\")) return true;\n if (Array.isArray(n.children)) return n.children.some(hasStandardType);\n return false;\n}\n\n/** Guess the source format, or null when the caller must pass --format. */\nexport function detectFormat(fileName: string, content: string): string | null {\n const ext = extname(fileName).toLowerCase();\n const trimmed = content.trimStart();\n\n // MJML is decided before both the extension check and the generic `<` branch:\n // an MJML document saved as .html is still MJML, and reading it as HTML\n // silently produces a table-soup import that looks like a bad converter.\n if (ext === \".mjml\") return \"mjml\";\n if (/^<(\\?xml[^>]*\\?>\\s*)?<?\\s*mjml[\\s>]/i.test(trimmed)) return \"mjml\";\n if (/^<\\s*mj-body[\\s>]/i.test(trimmed)) return \"mjml\";\n\n // Stripo's compiled File→HTML and its plugin storage are both `.html` (or a\n // JSON `{ html, css }` blob), so the class check must run before the generic\n // html branch or every Stripo export imports as table soup.\n if (looksLikeStripoHtml(content)) return \"stripo\";\n\n if (ext === \".html\" || ext === \".htm\") return \"html\";\n if (trimmed.startsWith(\"<\")) return \"html\";\n if (trimmed.startsWith(\"{\")) {\n let obj: {\n body?: { rows?: unknown; type?: unknown };\n page?: { rows?: unknown };\n tagName?: unknown;\n content?: { type?: unknown };\n type?: unknown;\n html?: unknown;\n };\n try {\n obj = JSON.parse(content);\n } catch {\n return null;\n }\n // Unlayer designs come from editor.saveDesign(): { body: { rows } }.\n if (obj?.body?.rows) return \"unlayer\";\n // BeeFree templates: { page: { rows } }.\n if (obj?.page?.rows) return \"beefree\";\n // Topol designs are an MJML-shaped tree rooted at the global style.\n if (obj?.tagName === \"mj-global-style\") return \"topol\";\n // Chamaileon getDocument(): { body: { type: \"body\" } }. Unlayer's\n // { body: { rows } } is matched above, so the order between them matters.\n if (obj?.body?.type === \"body\") return \"chamaileon\";\n // Easy Email Pro persist: { content: { type: \"page\" } } or a bare page,\n // distinguished from OSS Easy Email by its `standard-*` node types.\n const page =\n obj?.content?.type === \"page\"\n ? obj.content\n : obj?.type === \"page\"\n ? obj\n : null;\n if (page && hasStandardType(page)) return \"easy-email-pro\";\n // A plugin host often persists the whole getTemplateData() object.\n if (typeof obj?.html === \"string\" && looksLikeStripoHtml(obj.html)) {\n return \"stripo\";\n }\n return null;\n }\n return null;\n}\n\n/** Split a Stripo source into html + css, whichever shape it arrived in. */\nexport function unpackStripoSource(source: string): {\n html: string;\n css?: string;\n} {\n const trimmed = source.trimStart();\n if (trimmed.startsWith(\"{\")) {\n try {\n const obj = JSON.parse(source) as { html?: unknown; css?: unknown };\n if (typeof obj?.html === \"string\") {\n return {\n html: obj.html,\n css: typeof obj.css === \"string\" ? obj.css : undefined,\n };\n }\n } catch {\n // Not an envelope after all — treat the whole thing as markup.\n }\n }\n return { html: source };\n}\n\n/** The stylesheet a plugin host wrote beside an HTML export, if there is one. */\nfunction siblingCss(sourcePath: string, source: string): string | undefined {\n // A JSON envelope carries its own css; only a bare .html file has a sibling.\n if (source.trimStart().startsWith(\"{\")) return undefined;\n const cssPath = sourcePath.replace(/\\.[^.]+$/, \".css\");\n if (cssPath === sourcePath || !existsSync(cssPath)) return undefined;\n return readFileSync(cssPath, \"utf8\");\n}\n\nexport interface ReportCounts {\n total: number;\n converted: number;\n approximated: number;\n htmlFallback: number;\n skipped: number;\n warnings: string[];\n}\n\n/**\n * Status counts from a converter's report. Derived from `report.entries` (every\n * entry carries a `status`) rather than each package's own `summary` shape, so\n * it works identically across all three converters and any future one.\n */\nexport function summarizeReport(report: unknown): ReportCounts {\n const r = report as\n { entries?: Array<{ status?: string }>; warnings?: string[] } | undefined;\n const counts: ReportCounts = {\n total: 0,\n converted: 0,\n approximated: 0,\n htmlFallback: 0,\n skipped: 0,\n warnings: r?.warnings ?? [],\n };\n for (const e of r?.entries ?? []) {\n counts.total++;\n if (e.status === \"converted\") counts.converted++;\n else if (e.status === \"approximated\") counts.approximated++;\n else if (e.status === \"html-fallback\") counts.htmlFallback++;\n else if (e.status === \"skipped\") counts.skipped++;\n }\n return counts;\n}\n\nasync function listFormats(cwd: string): Promise<number> {\n const formats: Array<{\n format: string;\n package: string;\n available: boolean;\n }> = [];\n for (const [format, spec] of Object.entries(FORMATS)) {\n formats.push({\n format,\n package: spec.pkg,\n available: (await resolveOptional(spec.pkg, cwd)) !== null,\n });\n }\n emit({ formats }, () =>\n formats\n .map(\n (f) =>\n ` ${f.format.padEnd(14)} ${f.package}${f.available ? \"\" : \" (not installed)\"}`,\n )\n .join(\"\\n\"),\n );\n return EXIT.ok;\n}\n\nexport async function runImport(args: ParsedArgs): Promise<number> {\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n if (args.flags[\"list-formats\"]) return listFormats(cwd);\n\n const file = args.positional[0];\n if (!file) throw new UsageError(\"import needs a source file.\");\n const path = resolveFrom(file, cwd);\n\n let source: string;\n try {\n source = readFileSync(path, \"utf8\");\n } catch {\n throw new UsageError(`Could not read ${file}`);\n }\n\n const known = Object.keys(FORMATS).join(\", \");\n const requested = flagValue(args, \"format\");\n if (requested && !FORMATS[requested]) {\n throw new UsageError(\n `Unknown --format \"${requested}\". Known formats: ${known}.`,\n );\n }\n const format = requested ?? detectFormat(path, source);\n if (!format) {\n throw new UsageError(\n `Could not detect the format of ${file}. Pass --format with one of: ${known}.`,\n );\n }\n\n const spec = FORMATS[format];\n const mod = await resolveOptional<Record<string, unknown>>(spec.pkg, cwd);\n if (!mod) {\n throw new MissingDependencyError(\n spec.pkg,\n `Importing ${format} needs ${spec.pkg}, which isn't installed.\\n npm install ${spec.pkg}`,\n );\n }\n const convert = mod[spec.fn] as (\n input: unknown,\n options?: unknown,\n ) => {\n content: unknown;\n report: unknown;\n };\n\n let content: unknown;\n let report: unknown;\n if (spec.input === \"stripo\") {\n // Plugin storage keeps the stylesheet out of the markup — either beside the\n // html in one JSON envelope, or as a sibling .css file the host wrote next\n // to it. Without the CSS every block imports unstyled, which reads as a\n // broken converter rather than a missing file.\n const unpacked = unpackStripoSource(source);\n const css = unpacked.css ?? siblingCss(path, source);\n ({ content, report } = convert(unpacked.html, css ? { css } : undefined));\n } else {\n const input = spec.input === \"json\" ? JSON.parse(source) : source;\n ({ content, report } = convert(input));\n }\n\n const outName = flagValue(args, \"out\") ?? basename(path, extname(path));\n const written = writeTemplateFile(\n `${WORKING_DIR}/${outName}.json`,\n content,\n cwd,\n );\n\n const counts = summarizeReport(report);\n const lossy = counts.htmlFallback + counts.skipped > 0;\n emit({ format, file: written, report: counts }, () =>\n [\n `Imported ${format} to ${written}`,\n ` ${counts.converted} converted, ${counts.approximated} approximated, ${counts.htmlFallback} html fallback, ${counts.skipped} skipped`,\n ...counts.warnings.map((w) => ` ! ${w}`),\n lossy\n ? \" Import is lossy. Open it in live mode and refine the fallback blocks.\"\n : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n );\n return EXIT.ok;\n}\n","import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport {\n EXIT,\n InvalidTemplateError,\n readTemplateFile,\n resolveFrom,\n UsageError,\n} from \"../io\";\nimport { loadMjml, requireMjml } from \"../mjml\";\nimport {\n buildSpecimen,\n buildSpecimenTemplate,\n checkCustomBlock,\n checkRecipe,\n renderSpecimenMjml,\n runRecipe,\n SPECIMEN_STATES,\n validateCustomBlockDefinition,\n type CustomBlockIssue,\n type CustomBlockWorkingFile,\n type SpecimenState,\n} from \"../../custom-block\";\n\nconst USAGE =\n \"Use `custom-block validate <file>`, `custom-block render <file>` or `custom-block fetch <file>`.\";\n\nfunction formatIssues(issues: CustomBlockIssue[]): string {\n return issues\n .map(\n (i) =>\n ` - [${i.severity}] ${i.ruleId}${i.path ? ` ${i.path}` : \"\"}: ${i.message}`,\n )\n .join(\"\\n\");\n}\n\n/** `--values '<json>'`: field values a recipe runs with, over the defaults. */\nexport function parseValuesFlag(\n raw: string | undefined,\n): Record<string, unknown> {\n if (raw === undefined) return {};\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new UsageError(\"--values must be a JSON object.\");\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new UsageError(\"--values must be a JSON object.\");\n }\n return parsed as Record<string, unknown>;\n}\n\nfunction loadValid(file: string, cwd: string): CustomBlockWorkingFile {\n const data = readTemplateFile(file, cwd);\n const r = validateCustomBlockDefinition(data);\n if (!r.valid) {\n throw new InvalidTemplateError(\n `${file} is not a valid custom block definition.`,\n r.issues.filter((i) => i.severity === \"error\").map((i) => i.message),\n );\n }\n return data as CustomBlockWorkingFile;\n}\n\nexport async function runCustomBlock(args: ParsedArgs): Promise<number> {\n const sub = args.positional[0];\n const file = args.positional[1];\n if (sub !== \"validate\" && sub !== \"render\" && sub !== \"fetch\") {\n throw new UsageError(`Unknown \"custom-block ${sub ?? \"\"}\". ${USAGE}`);\n }\n if (!file)\n throw new UsageError(`custom-block ${sub} needs a definition file.`);\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n\n if (sub === \"validate\") {\n const mjml = await loadMjml(cwd);\n const result = await checkCustomBlock(readTemplateFile(file, cwd), {\n compileMjml: mjml\n ? async (m) => ({\n errors: (await mjml(m, { validationLevel: \"soft\" })).errors,\n })\n : undefined,\n });\n if (!mjml)\n note(\"`mjml` isn't installed, so the HTML compile check was skipped.\");\n emit(result, () =>\n result.issues.length === 0\n ? \"✓ Valid custom block — no issues\"\n : `${result.valid ? \"✓ Valid\" : \"✗ Invalid\"} · ${result.issues.length} issue(s):\\n${formatIssues(result.issues)}`,\n );\n return result.valid ? EXIT.ok : EXIT.invalid;\n }\n\n const def = loadValid(file, cwd);\n\n if (sub === \"render\") {\n const state = flagValue(args, \"state\");\n if (\n state !== undefined &&\n !(SPECIMEN_STATES as readonly string[]).includes(state)\n ) {\n throw new UsageError(\n `Unknown --state \"${state}\". Use one of: ${SPECIMEN_STATES.join(\", \")}.`,\n );\n }\n // A state the definition doesn't produce would render a sheet without the\n // block, which reads as a block that renders nothing.\n if (state !== undefined) {\n const states = buildSpecimen(def).map((i) => i.state);\n if (!(states as string[]).includes(state)) {\n throw new UsageError(\n `${file} has no \"${state}\" state: it has no ${state === \"flipped\" ? \"boolean\" : \"repeatable\"} field. Its states: ${states.join(\", \")}.`,\n );\n }\n }\n const format = flagValue(args, \"format\") ?? \"mjml\";\n if (format !== \"mjml\" && format !== \"html\")\n throw new UsageError(`Unknown --format \"${format}\". Use mjml or html.`);\n let output: string;\n try {\n output = await renderSpecimenMjml(\n def,\n buildSpecimenTemplate(\n def,\n undefined,\n state as SpecimenState | undefined,\n ),\n );\n } catch (err) {\n throw new InvalidTemplateError(`${file} does not render.`, [\n (err as Error).message,\n ]);\n }\n if (format === \"html\")\n output = (\n await (\n await requireMjml(cwd, \"Rendering HTML\")\n )(output, { validationLevel: \"soft\" })\n ).html;\n const out = flagValue(args, \"out\", \"o\");\n if (out) {\n const path = resolveFrom(out, cwd);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, output, \"utf8\");\n note(`Wrote ${path}`);\n return EXIT.ok;\n }\n emit({ format, output }, () => output);\n return EXIT.ok;\n }\n\n // sub === \"fetch\"\n if (!def.dataSourcePreview)\n throw new UsageError(`${file} has no \\`dataSourcePreview\\` to run.`);\n const recipeErrors = checkRecipe(def).filter((i) => i.severity === \"error\");\n if (recipeErrors.length > 0) {\n throw new InvalidTemplateError(\n `${file} has a dataSourcePreview the bridge would refuse to run.`,\n recipeErrors.map((i) => `${i.ruleId}: ${i.message}`),\n );\n }\n const overrides = parseValuesFlag(flagValue(args, \"values\"));\n const defaults = buildSpecimen(def)[0].fieldValues;\n const result = await runRecipe(def.dataSourcePreview, {\n ...defaults,\n ...overrides,\n });\n emit(result, () =>\n result.ok\n ? `✓ ${result.status} · ${JSON.stringify(result.values, null, 2)}${result.unmapped.length ? `\\nUnmapped: ${result.unmapped.join(\", \")}` : \"\"}`\n : `✗ ${result.error}`,\n );\n return result.ok ? EXIT.ok : EXIT.invalid;\n}\n","import { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve } from \"node:path\";\nimport { flagValue, type ParsedArgs } from \"../args\";\nimport { emit, note } from \"../output\";\nimport { EXIT, resolveFrom, UsageError } from \"../io\";\nimport { parseValuesFlag } from \"./custom-block\";\nimport {\n DEFAULT_PORT,\n listWorkingFiles,\n type FetchSummary,\n type LiveMode,\n type PidfileInfo,\n openBrowser,\n pidfilePath,\n processAlive,\n readPidfile,\n readWorkingFile,\n startBridgePreferring,\n WORKING_DIR,\n} from \"../../live/index\";\n\n/**\n * The first title block's text in document order, as a hint for `list`.\n *\n * It must descend into a section's columns, not scan top-level blocks only:\n * templates put their content inside sections (that is the documented\n * structure), so a top-level scan finds nothing for a real template. Measured\n * across all five of the templatical skill's examples — event-invite, newsletter,\n * product-sale, receipt, welcome — none has a top-level title block, so a\n * shallow version of this returns null every time and the hint is dead code.\n */\nfunction titleHint(content: unknown): string | null {\n return findTitle((content as { blocks?: unknown })?.blocks);\n}\n\nfunction findTitle(blocks: unknown): string | null {\n if (!Array.isArray(blocks)) return null;\n for (const block of blocks as Array<{\n type?: string;\n content?: string;\n children?: unknown[];\n }>) {\n if (block?.type === \"title\" && typeof block.content === \"string\") {\n return block.content;\n }\n if (block?.type === \"section\" && Array.isArray(block.children)) {\n for (const column of block.children) {\n const hit = findTitle(column);\n if (hit) return hit;\n }\n }\n }\n return null;\n}\n\nexport function runList(args: ParsedArgs): number {\n const cwd = flagValue(args, \"cwd\") ?? process.cwd();\n const templates = listWorkingFiles(cwd).map((name) => ({\n name,\n title: titleHint(readWorkingFile(join(cwd, WORKING_DIR, name))),\n }));\n emit({ templates }, () =>\n templates.length === 0\n ? `No templates in ${WORKING_DIR}/`\n : templates\n .map((t) => ` ${t.name}${t.title ? ` - ${t.title}` : \"\"}`)\n .join(\"\\n\"),\n );\n return EXIT.ok;\n}\n\nasync function postTo(\n port: number,\n path: string,\n body?: unknown,\n): Promise<Response> {\n return fetch(`http://localhost:${port}${path}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n}\n\n/** The fetched state's outcome, as one line of reload or start output. */\nfunction fetchLine(fetch: FetchSummary): string {\n if (!fetch.ok) return `Fetched state: the request failed. ${fetch.error}`;\n return fetch.unmapped.length > 0\n ? `Fetched state: filled from the API; not in the response: ${fetch.unmapped.join(\", \")}.`\n : \"Fetched state: filled from the API.\";\n}\n\nexport async function runLive(args: ParsedArgs): Promise<number> {\n const sub = args.positional[0];\n const cwd = resolveFrom(flagValue(args, \"cwd\") ?? \".\", process.cwd());\n\n if (sub === \"reload\" || sub === \"stop\") {\n const info = readPidfile(cwd);\n if (!info || !processAlive(info.pid)) {\n throw new UsageError(\n `No live server is running here (no live pidfile at ${WORKING_DIR}/live-server.pid).`,\n );\n }\n if (sub === \"reload\") {\n const consumeAnnotations = args.flags[\"consume-annotations\"] === true;\n const res = await postTo(\n info.port,\n \"/reload\",\n consumeAnnotations ? { consumeAnnotations: true } : undefined,\n );\n const body = (await res.json().catch(() => ({}))) as {\n ok?: boolean;\n clients?: number;\n consumed?: boolean;\n mode?: LiveMode;\n error?: string;\n fetch?: FetchSummary;\n };\n const clients = body.clients ?? 0;\n const consumed = body.consumed === true;\n // The bridge's error already names what failed and what to run; a\n // refused request (a 403 from a rewritten Host, say) has no body at all.\n if (res.status >= 400 || body.ok === false) {\n const reason =\n body.error ??\n (res.status >= 400\n ? `The live server refused the reload (${res.status}).`\n : \"The live server reported a failed reload.\");\n const error = `${reason} Nothing was pushed.`;\n emit(\n { reloaded: false, ok: false, clients, consumed, error },\n () => error,\n );\n return EXIT.invalid;\n }\n const fetch = body.fetch;\n emit(\n {\n reloaded: true,\n ok: true,\n clients,\n consumed,\n ...(fetch ? { fetch } : {}),\n },\n () =>\n [\n body.mode === \"custom-block\"\n ? `Pushed the custom block to ${clients} page(s).`\n : `Pushed the working file to ${clients} connected page(s).`,\n fetch ? fetchLine(fetch) : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n );\n return EXIT.ok;\n }\n try {\n process.kill(info.pid, \"SIGTERM\");\n } catch {\n /* already gone */\n }\n rmSync(pidfilePath(cwd), { force: true });\n emit({ stopped: true }, () => \"Stopped the live server.\");\n return EXIT.ok;\n }\n\n if (sub !== undefined) {\n throw new UsageError(\n `Unknown \"live ${sub}\". Use \\`live\\`, \\`live reload\\` or \\`live stop\\`.`,\n );\n }\n\n // start\n const portFlag = flagValue(args, \"port\");\n const preferredPort = portFlag ? Number(portFlag) : DEFAULT_PORT;\n const file = flagValue(args, \"file\");\n const customBlock = flagValue(args, \"custom-block\");\n const host = flagValue(args, \"host\");\n if (customBlock && file) {\n throw new UsageError(\"Pass either --file or --custom-block, not both.\");\n }\n if (host && !customBlock) {\n throw new UsageError(\"--host only applies with --custom-block.\");\n }\n const fetchOn = args.flags.fetch === true;\n if (fetchOn && !customBlock) {\n throw new UsageError(\"--fetch only applies with --custom-block.\");\n }\n const valuesRaw = flagValue(args, \"values\");\n if (valuesRaw !== undefined && !fetchOn) {\n throw new UsageError(\"--values only applies with --fetch.\");\n }\n const fetchValues = fetchOn ? parseValuesFlag(valuesRaw) : undefined;\n const abs = (p: string) => (isAbsolute(p) ? p : resolve(cwd, p));\n const requested: Required<Pick<PidfileInfo, \"mode\" | \"path\">> &\n Pick<PidfileInfo, \"host\" | \"fetch\"> = {\n mode: customBlock ? \"custom-block\" : \"template\",\n path: abs(customBlock ?? file ?? join(WORKING_DIR, \"template.json\")),\n ...(host ? { host: abs(host) } : {}),\n ...(fetchValues ? { fetch: fetchValues } : {}),\n };\n\n const existing = readPidfile(cwd);\n if (existing && processAlive(existing.pid)) {\n // A pidfile without `mode` came from a template-only bridge; one without\n // `path` can't be compared, so only its mode is.\n const mode = existing.mode ?? \"template\";\n if (\n mode !== requested.mode ||\n (existing.path !== undefined && existing.path !== requested.path) ||\n (mode === \"custom-block\" &&\n (existing.host !== requested.host ||\n JSON.stringify(existing.fetch) !== JSON.stringify(requested.fetch)))\n ) {\n throw new UsageError(\n `A live server is already running for ${existing.path ?? \"another file\"} (${mode}). Run \\`templatical live stop\\` first.`,\n );\n }\n emit(\n {\n url: `http://localhost:${existing.port}/`,\n pid: existing.pid,\n alreadyRunning: true,\n },\n () =>\n `Live server already running (pid ${existing.pid}) at http://localhost:${existing.port}/`,\n );\n return EXIT.ok;\n }\n if (existing) rmSync(pidfilePath(cwd), { force: true }); // stale\n\n const handle = await startBridgePreferring({\n cwd,\n preferredPort,\n file,\n customBlock,\n host,\n ...(fetchValues ? { fetch: { values: fetchValues } } : {}),\n });\n\n mkdirSync(dirname(pidfilePath(cwd)), { recursive: true });\n writeFileSync(\n pidfilePath(cwd),\n JSON.stringify({\n pid: process.pid,\n port: handle.port,\n ...requested,\n path: handle.workingPath,\n } satisfies PidfileInfo),\n \"utf8\",\n );\n\n const cleanup = () => {\n rmSync(pidfilePath(cwd), { force: true });\n handle.close().finally(() => process.exit(EXIT.ok));\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n // Emit before blocking: the caller needs the URL and the working path now,\n // not when the server shuts down.\n const fetch = handle.lastFetch();\n emit(\n {\n url: handle.url,\n port: handle.port,\n preferredPort,\n fellBack: handle.fellBack,\n workingFile: handle.workingPath,\n customBlock: customBlock ? handle.workingPath : undefined,\n fetch: fetch ?? undefined,\n },\n () =>\n [\n `Templatical live preview running at ${handle.url}`,\n handle.fellBack\n ? `(port ${preferredPort} was busy - using ${handle.port})`\n : \"\",\n `${customBlock ? \"Custom block\" : \"Working file\"}: ${handle.workingPath}`,\n fetch ? fetchLine(fetch) : \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\"),\n );\n\n if (args.flags[\"no-open\"]) {\n note(`Open ${handle.url} in a browser.`);\n } else {\n note(`Opening ${handle.url} in your default browser...`);\n openBrowser(handle.url);\n }\n note(\"After writing the working file, run: templatical live reload\");\n\n // Block: the bridge owns the process until a signal arrives.\n return new Promise<number>(() => {});\n}\n","#!/usr/bin/env node\n// The CLI entry point.\n//\n// Every command is a thin shell over this package's library functions; the\n// dispatch below owns argument handling, the error→exit-code mapping, and\n// nothing else. Human-facing output goes through cli/output.ts.\n\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { parseArgs } from \"./cli/args\";\nimport { note, setJsonMode } from \"./cli/output\";\nimport {\n EXIT,\n InvalidTemplateError,\n MissingDependencyError,\n UsageError,\n} from \"./cli/io\";\nimport { runSchema } from \"./cli/commands/schema\";\nimport { runValidate } from \"./cli/commands/validate\";\nimport { runRender } from \"./cli/commands/render\";\nimport { runEdit } from \"./cli/commands/edit\";\nimport { runImport } from \"./cli/commands/import\";\nimport { runCustomBlock } from \"./cli/commands/custom-block\";\nimport { runList, runLive } from \"./cli/commands/live\";\n\nconst USAGE = `templatical <command> [options]\n\n validate <file> structural + quality lint\n schema [--out <file>] print the block JSON Schema\n render <file> [--format mjml|html] [-o <file>] render to MJML or HTML\n edit <file> --op '<json>' | --ops <file> apply operations, write the result\n import <file> [--format <fmt>] | --list-formats convert a design to Templatical JSON\n live [--file <f>] [--port <n>] [--cwd <d>] [--no-open]\n live reload | live stop\n custom-block validate <file> check a custom block definition\n custom-block render <file> [--state <s>] [--format mjml|html] [-o <file>]\n custom-block fetch <file> [--values '<json>'] run its dataSourcePreview once\n live --custom-block <file> [--host <template>] [--fetch [--values '<json>']]\n preview a custom block\n list working files in .templatical/\n\nOptions:\n --json machine-readable output on stdout\n`;\n\nexport async function main(argv: string[]): Promise<number> {\n const args = parseArgs(argv);\n setJsonMode(args.json);\n\n switch (args.command) {\n case \"validate\":\n return runValidate(args);\n case \"schema\":\n return runSchema(args);\n case \"render\":\n return await runRender(args);\n case \"edit\":\n return runEdit(args);\n case \"import\":\n return await runImport(args);\n case \"live\":\n return await runLive(args);\n case \"list\":\n return runList(args);\n case \"custom-block\":\n return await runCustomBlock(args);\n case \"help\":\n note(USAGE);\n return EXIT.ok;\n case undefined:\n // parseArgs strips leading dashes into flags, so `--help`/`-h` never\n // reach args.command — they land here, not on a (dead) case \"--help\".\n // Asking for help is not a usage error; a bare invocation still is.\n if (args.flags.help === true || args.flags.h === true) {\n note(USAGE);\n return EXIT.ok;\n }\n note(USAGE);\n return EXIT.usage;\n default:\n note(`Unknown command \"${args.command}\".\\n\\n${USAGE}`);\n return EXIT.usage;\n }\n}\n\n// Only self-invoke when run as the CLI entry point, not when imported — this\n// package's own tests import `main` directly to exercise the dispatch above\n// without spawning a subprocess.\n//\n// import.meta.url is always a realpath, but process.argv[1] is not: npm's\n// `bin` field links a real symlink into the consumer's node_modules/.bin\n// (e.g. node_modules/.bin/templatical -> ../@templatical/template-tools/dist/bin.js),\n// and Node leaves argv[1] as that symlink path instead of resolving it.\n// Comparing them directly is false for every consumer who runs the published\n// binary, so main() would silently never run. realpathSync(argv[1]) resolves\n// the symlink so both sides name the same file.\nexport function isEntryPoint(): boolean {\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return pathToFileURL(realpathSync(argv1)).href === import.meta.url;\n } catch {\n // realpathSync throws (e.g. ENOENT) if argv[1] doesn't exist or can't be\n // read. This guard runs at module load, so failing open into a crash\n // would be worse than just not self-invoking.\n return false;\n }\n}\n\nif (isEntryPoint()) {\n main(process.argv.slice(2))\n .then((code) => process.exit(code))\n .catch((err: unknown) => {\n if (err instanceof InvalidTemplateError) {\n note(err.message);\n for (const e of err.errors) note(` - ${e}`);\n process.exit(EXIT.invalid);\n }\n if (err instanceof MissingDependencyError) {\n note(err.message);\n process.exit(EXIT.missingDep);\n }\n if (err instanceof UsageError) {\n note(err.message);\n process.exit(EXIT.usage);\n }\n note(`Unexpected error: ${(err as Error)?.message ?? String(err)}`);\n process.exit(EXIT.usage);\n });\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LintIssue } from "@templatical/quality";
2
- import { ColumnLayout, CustomBlockDefinition, TemplateContent, TemplateOperationPayload } from "@templatical/types";
2
+ import { CustomBlockDefinition, OperationError, applyOperations, getColumnCount } from "@templatical/types";
3
3
  import { Liquid } from "liquidjs";
4
4
  //#region src/validate.d.ts
5
5
  interface SchemaDefinition {
@@ -39,18 +39,6 @@ interface QualityLintResult {
39
39
  */
40
40
  export declare function runQualityLint(data: unknown): QualityLintResult;
41
41
  //#endregion
42
- //#region src/operations.d.ts
43
- /** Columns a layout declares. Mirrors the helper in core's editor. */
44
- export declare function getColumnCount(layout: ColumnLayout): number;
45
- interface OperationResult {
46
- ok: boolean;
47
- /** The new document on success; the unchanged input on failure. */
48
- content: TemplateContent;
49
- error?: string;
50
- }
51
- /** Apply one operation, returning a new document. Never mutates the input. */
52
- export declare function applyOperation(content: TemplateContent, payload: TemplateOperationPayload): OperationResult;
53
- //#endregion
54
42
  //#region src/custom-block/types.d.ts
55
43
  type CustomBlockIssueSeverity = "error" | "warning";
56
44
  interface CustomBlockIssue {
@@ -97,5 +85,5 @@ export declare function validateCustomBlockDefinition(data: unknown): CustomBloc
97
85
  type SpecimenState = "defaults" | "empty" | "long" | "min-items" | "max-items" | "flipped";
98
86
  export declare const SPECIMEN_STATES: readonly SpecimenState[];
99
87
  //#endregion
100
- export type { CheckCustomBlockOptions, CustomBlockCheckResult, CustomBlockIssue, CustomBlockIssueSeverity, CustomBlockWorkingFile, DataSourcePreview, OperationResult, QualityLintResult, SpecimenState, ValidationResult };
88
+ export { type CheckCustomBlockOptions, type CustomBlockCheckResult, type CustomBlockIssue, type CustomBlockIssueSeverity, type CustomBlockWorkingFile, type DataSourcePreview, OperationError, type QualityLintResult, type SpecimenState, type ValidationResult, applyOperations, getColumnCount };
101
89
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
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
- export { SPECIMEN_STATES, applyOperation, checkCustomBlock, getColumnCount, runQualityLint, schema, validateCustomBlockDefinition, validateTemplate };
2
+ import { OperationError, applyOperations, getColumnCount } from "@templatical/types";
3
+ export { OperationError, SPECIMEN_STATES, applyOperations, 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.48.0";
3
+ export declare const EDITOR_VERSION = "0.49.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;
@@ -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.48.0";
10
+ const EDITOR_VERSION = "0.49.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.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
+ {"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.49.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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"}
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.48.0",
4
+ "version": "0.49.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.48.0",
11
- "@templatical/renderer": "0.48.0",
12
- "@templatical/types": "0.48.0",
10
+ "@templatical/quality": "0.49.0",
11
+ "@templatical/renderer": "0.49.0",
12
+ "@templatical/types": "0.49.0",
13
13
  "ajv": "^8.20.0",
14
14
  "htmlparser2": "^12.0.0",
15
15
  "liquidjs": "^10.30.0"
@@ -19,7 +19,7 @@
19
19
  "mjml": "^5.4.1",
20
20
  "ts-json-schema-generator": "^2.9.0",
21
21
  "typescript": "^6.0.3",
22
- "vitest": "^5.0.4"
22
+ "vitest": "^5.0.5"
23
23
  },
24
24
  "exports": {
25
25
  ".": {
@@ -52,14 +52,14 @@
52
52
  "license": "MIT",
53
53
  "module": "./dist/index.js",
54
54
  "peerDependencies": {
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",
55
+ "@templatical/import-beefree": "0.49.0",
56
+ "@templatical/import-chamaileon": "0.49.0",
57
+ "@templatical/import-easy-email-pro": "0.49.0",
58
+ "@templatical/import-html": "0.49.0",
59
+ "@templatical/import-mjml": "0.49.0",
60
+ "@templatical/import-stripo": "0.49.0",
61
+ "@templatical/import-topol": "0.49.0",
62
+ "@templatical/import-unlayer": "0.49.0",
63
63
  "mjml": "^5.4.1"
64
64
  },
65
65
  "peerDependenciesMeta": {
@@ -1,172 +0,0 @@
1
- import "./custom-block-CsZN9qMM.js";
2
- import { safeClone } from "@templatical/types";
3
- //#region src/operations.ts
4
- /** Columns a layout declares. Mirrors the helper in core's editor. */
5
- function getColumnCount(layout) {
6
- if (layout === "1") return 1;
7
- if (layout === "3") return 3;
8
- return 2;
9
- }
10
- function findBlockById(blocks, id) {
11
- for (const block of blocks) {
12
- if (block.id === id) return block;
13
- if (block.type === "section") for (const column of block.children) {
14
- const found = findBlockById(column, id);
15
- if (found) return found;
16
- }
17
- }
18
- return null;
19
- }
20
- function findBlockParent(blocks, id, parent = { blocks }) {
21
- for (const block of blocks) {
22
- if (block.id === id) return parent;
23
- if (block.type === "section") for (let colIdx = 0; colIdx < block.children.length; colIdx++) {
24
- const result = findBlockParent(block.children[colIdx], id, {
25
- blocks: block.children[colIdx],
26
- sectionId: block.id,
27
- columnIndex: colIdx
28
- });
29
- if (result) return result;
30
- }
31
- }
32
- return null;
33
- }
34
- function reject(content, error) {
35
- return {
36
- ok: false,
37
- content,
38
- error
39
- };
40
- }
41
- /**
42
- * Resolve the array an operation targets, enforcing the column rules.
43
- * Returns a string on rejection so callers can surface the reason.
44
- */
45
- function resolveTarget(content, targetSectionId, columnIndex) {
46
- if (!targetSectionId) return content.blocks;
47
- const section = findBlockById(content.blocks, targetSectionId);
48
- if (!section) return `No block with id "${targetSectionId}".`;
49
- if (section.type !== "section") return `Block "${targetSectionId}" is a ${section.type}, not a section — it has no columns.`;
50
- const count = getColumnCount(section.columns);
51
- if (columnIndex < 0 || columnIndex >= count) return `Column ${columnIndex} is out of range for a "${section.columns}" section (${count} column(s)).`;
52
- const children = section;
53
- children.children[columnIndex] = children.children[columnIndex] || [];
54
- return children.children[columnIndex];
55
- }
56
- function insertAt(target, block, index) {
57
- if (index !== void 0 && index < target.length) target.splice(index, 0, block);
58
- else target.push(block);
59
- }
60
- /** Apply one operation, returning a new document. Never mutates the input. */
61
- function applyOperation(content, payload) {
62
- const data = payload.data ?? {};
63
- switch (payload.operation) {
64
- case "setContent": {
65
- const next = data.content;
66
- if (!next || typeof next !== "object" || !Array.isArray(next.blocks)) return reject(content, "setContent needs `content` with a blocks array.");
67
- return {
68
- ok: true,
69
- content: safeClone(next)
70
- };
71
- }
72
- case "updateSettings": {
73
- const updates = data.settings;
74
- if (!updates || typeof updates !== "object") return reject(content, "updateSettings needs a `settings` object.");
75
- const draft = safeClone(content);
76
- draft.settings = {
77
- ...draft.settings,
78
- ...updates
79
- };
80
- return {
81
- ok: true,
82
- content: draft
83
- };
84
- }
85
- case "addBlock": {
86
- const block = data.block;
87
- if (!block || typeof block !== "object" || typeof block.id !== "string") return reject(content, "addBlock needs a `block` with an id.");
88
- const targetSectionId = data.targetSectionId;
89
- if (targetSectionId && block.type === "section") return reject(content, "A section cannot be nested inside a section column — MJML would drop it on export. Add it at the top level instead.");
90
- const draft = safeClone(content);
91
- if (findBlockById(draft.blocks, block.id)) return reject(content, `A block with id "${block.id}" already exists.`);
92
- const target = resolveTarget(draft, targetSectionId, data.columnIndex ?? 0);
93
- if (typeof target === "string") return reject(content, target);
94
- insertAt(target, safeClone(block), data.index);
95
- return {
96
- ok: true,
97
- content: draft
98
- };
99
- }
100
- case "updateBlock": {
101
- const blockId = data.blockId;
102
- const updates = data.updates;
103
- if (!blockId) return reject(content, "updateBlock needs a `blockId`.");
104
- if (!updates || typeof updates !== "object") return reject(content, "updateBlock needs an `updates` object.");
105
- const draft = safeClone(content);
106
- const block = findBlockById(draft.blocks, blockId);
107
- if (!block) return reject(content, `No block with id "${blockId}".`);
108
- if ("type" in updates && updates.type !== block.type) return reject(content, `Cannot change a block's type (${block.type} → ${String(updates.type)}). Delete it and add the new block instead.`);
109
- Object.assign(block, updates);
110
- return {
111
- ok: true,
112
- content: draft
113
- };
114
- }
115
- case "updateBlockStyle": {
116
- const blockId = data.blockId;
117
- const styles = data.styles;
118
- if (!blockId) return reject(content, "updateBlockStyle needs a `blockId`.");
119
- if (!styles || typeof styles !== "object") return reject(content, "updateBlockStyle needs a `styles` object.");
120
- const draft = safeClone(content);
121
- const block = findBlockById(draft.blocks, blockId);
122
- if (!block) return reject(content, `No block with id "${blockId}".`);
123
- block.styles = {
124
- ...block.styles,
125
- ...styles
126
- };
127
- return {
128
- ok: true,
129
- content: draft
130
- };
131
- }
132
- case "deleteBlock": {
133
- const blockId = data.blockId;
134
- if (!blockId) return reject(content, "deleteBlock needs a `blockId`.");
135
- const draft = safeClone(content);
136
- const parent = findBlockParent(draft.blocks, blockId);
137
- if (!parent) return reject(content, `No block with id "${blockId}".`);
138
- const index = parent.blocks.findIndex((b) => b.id === blockId);
139
- parent.blocks.splice(index, 1);
140
- return {
141
- ok: true,
142
- content: draft
143
- };
144
- }
145
- case "moveBlock": {
146
- const blockId = data.blockId;
147
- const index = data.index;
148
- if (!blockId) return reject(content, "moveBlock needs a `blockId`.");
149
- if (typeof index !== "number" || index < 0) return reject(content, "moveBlock needs a non-negative `index`.");
150
- const targetSectionId = data.targetSectionId;
151
- const draft = safeClone(content);
152
- const parent = findBlockParent(draft.blocks, blockId);
153
- if (!parent) return reject(content, `No block with id "${blockId}".`);
154
- const oldIndex = parent.blocks.findIndex((b) => b.id === blockId);
155
- if (targetSectionId && parent.blocks[oldIndex].type === "section") return reject(content, "A section cannot be moved into a section column — MJML would drop it on export.");
156
- if (targetSectionId === blockId) return reject(content, "A block cannot be moved into itself.");
157
- const target = resolveTarget(draft, targetSectionId, data.columnIndex ?? 0);
158
- if (typeof target === "string") return reject(content, target);
159
- const [block] = parent.blocks.splice(oldIndex, 1);
160
- target.splice(index, 0, block);
161
- return {
162
- ok: true,
163
- content: draft
164
- };
165
- }
166
- default: return reject(content, `Unknown operation "${String(payload.operation)}".`);
167
- }
168
- }
169
- //#endregion
170
- export { getColumnCount as n, applyOperation as t };
171
-
172
- //# sourceMappingURL=src-SZZkttBC.js.map
@@ -1 +0,0 @@
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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"}