@classic-homes/slides 0.4.0 → 0.5.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.
@@ -4,7 +4,7 @@ import {
4
4
  LAYOUT_CATEGORIES,
5
5
  baseSlide,
6
6
  getLayout
7
- } from "./chunk-PTTLL6PQ.js";
7
+ } from "./chunk-OUAMH2TP.js";
8
8
 
9
9
  // src/catalog.ts
10
10
  import { z } from "zod";
@@ -104,4 +104,4 @@ export {
104
104
  deckJsonSchema,
105
105
  AUTHORING_GUIDE
106
106
  };
107
- //# sourceMappingURL=chunk-JFUJ47TP.js.map
107
+ //# sourceMappingURL=chunk-TEE3OGRQ.js.map
package/dist/cli.js CHANGED
@@ -4,18 +4,19 @@ import {
4
4
  deckJsonSchema,
5
5
  describeLayout,
6
6
  listLayouts
7
- } from "./chunk-JFUJ47TP.js";
7
+ } from "./chunk-TEE3OGRQ.js";
8
8
  import {
9
9
  buildDeckFile,
10
+ exportDeckFile,
10
11
  fileImageResolver,
11
12
  loadDeckFile
12
- } from "./chunk-L7G3VH7K.js";
13
+ } from "./chunk-GX6P2SE6.js";
13
14
  import {
14
15
  DeckError,
15
16
  formatIssues,
16
17
  renderDeck,
17
18
  validateDeck
18
- } from "./chunk-PTTLL6PQ.js";
19
+ } from "./chunk-OUAMH2TP.js";
19
20
 
20
21
  // src/node/cli.ts
21
22
  import { watch } from "fs";
@@ -30,12 +31,14 @@ Usage
30
31
  classic-slides new <file.yaml> [--starter basic|journey] Start a deck from a starter
31
32
  classic-slides build <file.yaml> [-o out.html] Render one self-contained HTML file
32
33
  classic-slides dev <file.yaml> [--port 4321] Live preview; reloads on save
34
+ classic-slides export <file.yaml> [-o out.json] Portable JSON with images inlined, for import
33
35
  classic-slides validate <file.yaml> Check for errors and crowded slides
34
36
  classic-slides layouts [name] List layouts, or show one in detail
35
37
  classic-slides guide Print the authoring guide
36
38
  classic-slides schema Print the deck JSON Schema
37
39
  classic-slides mcp [--root dir] Run the MCP server on stdio
38
40
  `;
41
+ var IMPORT_WARN_MB = 10;
39
42
  var pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
40
43
  async function main() {
41
44
  const { positionals, values } = parseArgs({
@@ -76,6 +79,22 @@ Next: classic-slides dev ${basename(target)}`);
76
79
  console.log(`\u2714 ${r.out} \u2014 ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB`);
77
80
  return;
78
81
  }
82
+ case "export": {
83
+ if (!arg) throw new Error("Usage: classic-slides export <file.yaml>");
84
+ const r = await exportDeckFile(arg, {
85
+ out: values.out,
86
+ onWarning: (w) => console.warn(`\u26A0 ${w}`)
87
+ });
88
+ for (const w of r.warnings) console.warn(`\u26A0 Not inlined: ${w.src} \u2014 ${w.message}`);
89
+ const mb = r.bytes / 1024 / 1024;
90
+ console.log(`\u2714 ${r.out} \u2014 ${mb.toFixed(2)} MB`);
91
+ if (mb > IMPORT_WARN_MB)
92
+ console.warn(
93
+ `\u26A0 Over ${IMPORT_WARN_MB} MB; the deck editor may refuse it. Resize large photos (~2000px wide).`
94
+ );
95
+ process.exitCode = r.warnings.length ? 1 : 0;
96
+ return;
97
+ }
79
98
  case "validate": {
80
99
  if (!arg) throw new Error("Usage: classic-slides validate <file.yaml>");
81
100
  const v = validateDeck(await loadDeckFile(resolve(arg)));
@@ -107,7 +126,7 @@ ${"".padEnd(12)} Use for: ${l.whenToUse}
107
126
  case "mcp": {
108
127
  let mcp;
109
128
  try {
110
- mcp = await import("./mcp-UX32IP2J.js");
129
+ mcp = await import("./mcp-3WAVCLQ2.js");
111
130
  } catch (e) {
112
131
  if (e.code === "ERR_MODULE_NOT_FOUND") {
113
132
  throw new Error(
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/node/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * classic-slides — build branded Classic Homes decks from YAML.\n */\nimport { watch } from 'node:fs';\nimport { access, copyFile, mkdir } from 'node:fs/promises';\nimport { createServer } from 'node:http';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { AUTHORING_GUIDE, deckJsonSchema, describeLayout, listLayouts } from '../catalog.js';\nimport { renderDeck } from '../render.js';\nimport { DeckError, formatIssues, validateDeck } from '../validate.js';\nimport { buildDeckFile, fileImageResolver, loadDeckFile } from './index.js';\n\nconst HELP = `classic-slides — branded Classic Homes presentations\n\nUsage\n classic-slides new <file.yaml> [--starter basic|journey] Start a deck from a starter\n classic-slides build <file.yaml> [-o out.html] Render one self-contained HTML file\n classic-slides dev <file.yaml> [--port 4321] Live preview; reloads on save\n classic-slides validate <file.yaml> Check for errors and crowded slides\n classic-slides layouts [name] List layouts, or show one in detail\n classic-slides guide Print the authoring guide\n classic-slides schema Print the deck JSON Schema\n classic-slides mcp [--root dir] Run the MCP server on stdio\n`;\n\nconst pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\n\nasync function main() {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n out: { type: 'string', short: 'o' },\n port: { type: 'string', default: '4321' },\n starter: { type: 'string', default: 'basic' },\n root: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [cmd, arg] = positionals;\n\n if (!cmd || values.help) return void process.stdout.write(HELP);\n\n switch (cmd) {\n case 'new': {\n if (!arg) throw new Error('Usage: classic-slides new <file.yaml>');\n const target = resolve(arg.endsWith('.yaml') || arg.endsWith('.yml') ? arg : `${arg}.yaml`);\n if (\n await access(target).then(\n () => true,\n () => false\n )\n )\n throw new Error(`${target} already exists.`);\n const starter = join(pkgRoot, 'starters', `${values.starter}.yaml`);\n await mkdir(dirname(target), { recursive: true });\n await copyFile(starter, target);\n console.log(`Created ${target}\\nNext: classic-slides dev ${basename(target)}`);\n return;\n }\n case 'build': {\n if (!arg) throw new Error('Usage: classic-slides build <file.yaml>');\n const r = await buildDeckFile(arg, {\n out: values.out,\n onWarning: (w) => console.warn(`⚠ ${w}`),\n });\n if (r.warnings.length) console.warn(formatIssues(r.warnings));\n console.log(`✔ ${r.out} — ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB`);\n return;\n }\n case 'validate': {\n if (!arg) throw new Error('Usage: classic-slides validate <file.yaml>');\n const v = validateDeck(await loadDeckFile(resolve(arg)));\n console.log(v.issues.length ? formatIssues(v.issues) : '✔ Valid, no warnings');\n process.exitCode = v.ok ? 0 : 1;\n return;\n }\n case 'dev':\n return dev(arg, Number(values.port));\n case 'layouts': {\n if (arg) {\n const d = describeLayout(arg);\n if (!d) throw new Error(`Unknown layout \"${arg}\".`);\n console.log(JSON.stringify(d, null, 2));\n } else {\n for (const l of listLayouts())\n console.log(\n `${l.name.padEnd(12)} ${l.description}\\n${''.padEnd(12)} Use for: ${l.whenToUse}\\n`\n );\n }\n return;\n }\n case 'guide':\n return void console.log(AUTHORING_GUIDE);\n case 'schema':\n return void console.log(JSON.stringify(deckJsonSchema(), null, 2));\n case 'mcp': {\n // The MCP SDK is an optional peer so apps that only render decks don't install it.\n let mcp: typeof import('./mcp.js');\n try {\n mcp = await import('./mcp.js');\n } catch (e) {\n if ((e as { code?: string }).code === 'ERR_MODULE_NOT_FOUND') {\n throw new Error(\n 'The MCP server needs @modelcontextprotocol/sdk. Run it with:\\n npx -y -p @classic-homes/slides -p @modelcontextprotocol/sdk classic-slides mcp'\n );\n }\n throw e;\n }\n return mcp.runStdio(values.root);\n }\n default:\n throw new Error(`Unknown command \"${cmd}\".\\n\\n${HELP}`);\n }\n}\n\n/** Preview server: renders on every request and reloads the page when the deck folder changes. */\nasync function dev(file: string | undefined, port: number) {\n if (!file) throw new Error('Usage: classic-slides dev <file.yaml>');\n const abs = resolve(file);\n const dir = dirname(abs);\n const clients = new Set<import('node:http').ServerResponse>();\n const reload = `<script>new EventSource('/__reload').onmessage=()=>location.reload()</script>`;\n\n const errorPage = (msg: string) =>\n `<!doctype html><meta charset=\"utf-8\"><title>Deck error</title><body style=\"font:15px/1.5 ui-monospace,monospace;padding:40px;background:#fdf2f2;color:#7f1d1d\"><h2 style=\"font-family:system-ui\">Deck has problems</h2><pre style=\"white-space:pre-wrap\">${msg\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')}</pre>${reload}`;\n\n createServer(async (req, res) => {\n if (req.url === '/__reload') {\n res.writeHead(200, {\n 'content-type': 'text/event-stream',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n });\n res.write(': ok\\n\\n');\n clients.add(res);\n req.on('close', () => clients.delete(res));\n return;\n }\n let html: string;\n try {\n const deck = await loadDeckFile(abs);\n const r = await renderDeck(deck, {\n resolveImage: fileImageResolver(dir),\n embedSource: false,\n });\n html = r.html.replace('</body>', `${reload}</body>`);\n if (r.warnings.length) console.warn(formatIssues(r.warnings));\n } catch (e) {\n html = errorPage(\n e instanceof DeckError ? formatIssues(e.issues) : e instanceof Error ? e.message : String(e)\n );\n }\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });\n res.end(html);\n }).listen(port, () =>\n console.log(`Previewing ${file} at http://localhost:${port} (Ctrl+C to stop)`)\n );\n\n let timer: NodeJS.Timeout | undefined;\n watch(dir, { recursive: true }, (_, name) => {\n if (name && /\\.html$/.test(name)) return;\n clearTimeout(timer);\n timer = setTimeout(() => clients.forEach((c) => c.write('data: reload\\n\\n')), 120);\n });\n}\n\nmain().catch((e) => {\n console.error(e instanceof DeckError ? e.message : `✖ ${e instanceof Error ? e.message : e}`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/node/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * classic-slides — build branded Classic Homes decks from YAML.\n */\nimport { watch } from 'node:fs';\nimport { access, copyFile, mkdir } from 'node:fs/promises';\nimport { createServer } from 'node:http';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { AUTHORING_GUIDE, deckJsonSchema, describeLayout, listLayouts } from '../catalog.js';\nimport { renderDeck } from '../render.js';\nimport { DeckError, formatIssues, validateDeck } from '../validate.js';\nimport { buildDeckFile, exportDeckFile, fileImageResolver, loadDeckFile } from './index.js';\n\nconst HELP = `classic-slides — branded Classic Homes presentations\n\nUsage\n classic-slides new <file.yaml> [--starter basic|journey] Start a deck from a starter\n classic-slides build <file.yaml> [-o out.html] Render one self-contained HTML file\n classic-slides dev <file.yaml> [--port 4321] Live preview; reloads on save\n classic-slides export <file.yaml> [-o out.json] Portable JSON with images inlined, for import\n classic-slides validate <file.yaml> Check for errors and crowded slides\n classic-slides layouts [name] List layouts, or show one in detail\n classic-slides guide Print the authoring guide\n classic-slides schema Print the deck JSON Schema\n classic-slides mcp [--root dir] Run the MCP server on stdio\n`;\n\n/** Size above which hosted deck imports are likely to be refused. */\nconst IMPORT_WARN_MB = 10;\n\nconst pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\n\nasync function main() {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n out: { type: 'string', short: 'o' },\n port: { type: 'string', default: '4321' },\n starter: { type: 'string', default: 'basic' },\n root: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n },\n });\n const [cmd, arg] = positionals;\n\n if (!cmd || values.help) return void process.stdout.write(HELP);\n\n switch (cmd) {\n case 'new': {\n if (!arg) throw new Error('Usage: classic-slides new <file.yaml>');\n const target = resolve(arg.endsWith('.yaml') || arg.endsWith('.yml') ? arg : `${arg}.yaml`);\n if (\n await access(target).then(\n () => true,\n () => false\n )\n )\n throw new Error(`${target} already exists.`);\n const starter = join(pkgRoot, 'starters', `${values.starter}.yaml`);\n await mkdir(dirname(target), { recursive: true });\n await copyFile(starter, target);\n console.log(`Created ${target}\\nNext: classic-slides dev ${basename(target)}`);\n return;\n }\n case 'build': {\n if (!arg) throw new Error('Usage: classic-slides build <file.yaml>');\n const r = await buildDeckFile(arg, {\n out: values.out,\n onWarning: (w) => console.warn(`⚠ ${w}`),\n });\n if (r.warnings.length) console.warn(formatIssues(r.warnings));\n console.log(`✔ ${r.out} — ${r.slideCount} slides, ${(r.bytes / 1024 / 1024).toFixed(2)} MB`);\n return;\n }\n case 'export': {\n if (!arg) throw new Error('Usage: classic-slides export <file.yaml>');\n const r = await exportDeckFile(arg, {\n out: values.out,\n onWarning: (w) => console.warn(`⚠ ${w}`),\n });\n for (const w of r.warnings) console.warn(`⚠ Not inlined: ${w.src} — ${w.message}`);\n const mb = r.bytes / 1024 / 1024;\n console.log(`✔ ${r.out} — ${mb.toFixed(2)} MB`);\n if (mb > IMPORT_WARN_MB)\n console.warn(\n `⚠ Over ${IMPORT_WARN_MB} MB; the deck editor may refuse it. Resize large photos (~2000px wide).`\n );\n process.exitCode = r.warnings.length ? 1 : 0;\n return;\n }\n case 'validate': {\n if (!arg) throw new Error('Usage: classic-slides validate <file.yaml>');\n const v = validateDeck(await loadDeckFile(resolve(arg)));\n console.log(v.issues.length ? formatIssues(v.issues) : '✔ Valid, no warnings');\n process.exitCode = v.ok ? 0 : 1;\n return;\n }\n case 'dev':\n return dev(arg, Number(values.port));\n case 'layouts': {\n if (arg) {\n const d = describeLayout(arg);\n if (!d) throw new Error(`Unknown layout \"${arg}\".`);\n console.log(JSON.stringify(d, null, 2));\n } else {\n for (const l of listLayouts())\n console.log(\n `${l.name.padEnd(12)} ${l.description}\\n${''.padEnd(12)} Use for: ${l.whenToUse}\\n`\n );\n }\n return;\n }\n case 'guide':\n return void console.log(AUTHORING_GUIDE);\n case 'schema':\n return void console.log(JSON.stringify(deckJsonSchema(), null, 2));\n case 'mcp': {\n // The MCP SDK is an optional peer so apps that only render decks don't install it.\n let mcp: typeof import('./mcp.js');\n try {\n mcp = await import('./mcp.js');\n } catch (e) {\n if ((e as { code?: string }).code === 'ERR_MODULE_NOT_FOUND') {\n throw new Error(\n 'The MCP server needs @modelcontextprotocol/sdk. Run it with:\\n npx -y -p @classic-homes/slides -p @modelcontextprotocol/sdk classic-slides mcp'\n );\n }\n throw e;\n }\n return mcp.runStdio(values.root);\n }\n default:\n throw new Error(`Unknown command \"${cmd}\".\\n\\n${HELP}`);\n }\n}\n\n/** Preview server: renders on every request and reloads the page when the deck folder changes. */\nasync function dev(file: string | undefined, port: number) {\n if (!file) throw new Error('Usage: classic-slides dev <file.yaml>');\n const abs = resolve(file);\n const dir = dirname(abs);\n const clients = new Set<import('node:http').ServerResponse>();\n const reload = `<script>new EventSource('/__reload').onmessage=()=>location.reload()</script>`;\n\n const errorPage = (msg: string) =>\n `<!doctype html><meta charset=\"utf-8\"><title>Deck error</title><body style=\"font:15px/1.5 ui-monospace,monospace;padding:40px;background:#fdf2f2;color:#7f1d1d\"><h2 style=\"font-family:system-ui\">Deck has problems</h2><pre style=\"white-space:pre-wrap\">${msg\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')}</pre>${reload}`;\n\n createServer(async (req, res) => {\n if (req.url === '/__reload') {\n res.writeHead(200, {\n 'content-type': 'text/event-stream',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n });\n res.write(': ok\\n\\n');\n clients.add(res);\n req.on('close', () => clients.delete(res));\n return;\n }\n let html: string;\n try {\n const deck = await loadDeckFile(abs);\n const r = await renderDeck(deck, {\n resolveImage: fileImageResolver(dir),\n embedSource: false,\n });\n html = r.html.replace('</body>', `${reload}</body>`);\n if (r.warnings.length) console.warn(formatIssues(r.warnings));\n } catch (e) {\n html = errorPage(\n e instanceof DeckError ? formatIssues(e.issues) : e instanceof Error ? e.message : String(e)\n );\n }\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });\n res.end(html);\n }).listen(port, () =>\n console.log(`Previewing ${file} at http://localhost:${port} (Ctrl+C to stop)`)\n );\n\n let timer: NodeJS.Timeout | undefined;\n watch(dir, { recursive: true }, (_, name) => {\n if (name && /\\.html$/.test(name)) return;\n clearTimeout(timer);\n timer = setTimeout(() => clients.forEach((c) => c.write('data: reload\\n\\n')), 120);\n });\n}\n\nmain().catch((e) => {\n console.error(e instanceof DeckError ? e.message : `✖ ${e instanceof Error ? e.message : e}`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
@@ -601,4 +601,41 @@ declare function renderDeck(input: unknown, options?: RenderOptions): Promise<Re
601
601
  /** Pulls the embedded deck source back out of a rendered HTML file. */
602
602
  declare function extractDeckSource(html: string): unknown | undefined;
603
603
 
604
- export { checkPalette as A, type BrandPalette as B, CLASSIC_HOMES_BRAND as C, type Deck as D, collectImageSources as E, type FieldMarker as F, extractDeckSource as G, fieldMarkers as H, type ImageInput as I, formatIssues as J, formatPath as K, type LayoutCategory as L, getLayout as M, renderDeck as N, resolveBrand as O, PREVIEW_SIZE as P, validateBrandProfile as Q, type RenderOptions as R, type SlideData as S, validateDeck as T, type ValidateOptions as V, BrandPaletteSchema as a, type BrandProfile as b, type BrandProfileInput as c, BrandProfileSchema as d, CLASSIC_HOMES_PALETTE as e, type DeckBrand as f, DeckBrandSchema as g, DeckError as h, type DeckInput as i, DeckSchema as j, type DeckSettings as k, DeckSettingsSchema as l, type ImageResolver as m, type ImageValue as n, type ItemValue as o, LAYOUTS as p, LAYOUT_CATEGORIES as q, LAYOUT_CATEGORY_ORDER as r, type LayoutDef as s, type LayoutIssue as t, type LayoutName as u, type RenderResult as v, type ResolvedBrand as w, type SlideInput as x, SlideSchema as y, type ValidationResult as z };
604
+ /**
605
+ * Rewrites every image src in a deck: inline them as data URIs for a portable
606
+ * single-file export, or swap data URIs for hosted URLs on import.
607
+ */
608
+
609
+ interface MapImagesOptions extends ValidateOptions {
610
+ /** Images rewritten at once. Default 4. */
611
+ concurrency?: number;
612
+ }
613
+ interface InlineImagesResult {
614
+ /** A copy of the input deck with image srcs replaced. */
615
+ deck: unknown;
616
+ /** Images left as they were because the resolver failed. */
617
+ warnings: {
618
+ src: string;
619
+ message: string;
620
+ }[];
621
+ }
622
+ /**
623
+ * Calls `fn` once per distinct image src and returns a copy of `input` with
624
+ * each src replaced by the result. The deck is validated first so images
625
+ * written as plain strings (`image: photo.jpg`) are found too; the copy keeps
626
+ * the author's shape (strings stay strings, no schema defaults are added).
627
+ * Throws `DeckError` if the deck is invalid, or the first error from `fn`.
628
+ */
629
+ declare function mapDeckImages(input: unknown, fn: (src: string) => string | Promise<string>, options?: MapImagesOptions): Promise<unknown>;
630
+ /**
631
+ * Inlines every image as a data URI so the deck is one self-contained file.
632
+ * Images the resolver can't load are left as-is and reported in `warnings`.
633
+ */
634
+ declare function inlineDeckImages(input: unknown, resolve: ImageResolver, options?: MapImagesOptions): Promise<InlineImagesResult>;
635
+ /**
636
+ * Replaces every data-URI image with the src `upload` returns (e.g. a hosted
637
+ * URL). Other srcs are untouched. Errors from `upload` are thrown.
638
+ */
639
+ declare function externalizeDeckImages(input: unknown, upload: (dataUri: string) => string | Promise<string>, options?: MapImagesOptions): Promise<unknown>;
640
+
641
+ export { type ValidationResult as A, type BrandPalette as B, CLASSIC_HOMES_BRAND as C, type Deck as D, checkPalette as E, type FieldMarker as F, collectImageSources as G, externalizeDeckImages as H, type ImageInput as I, extractDeckSource as J, fieldMarkers as K, type LayoutCategory as L, type MapImagesOptions as M, formatIssues as N, formatPath as O, PREVIEW_SIZE as P, getLayout as Q, type RenderOptions as R, type SlideData as S, inlineDeckImages as T, mapDeckImages as U, type ValidateOptions as V, renderDeck as W, resolveBrand as X, validateBrandProfile as Y, validateDeck as Z, BrandPaletteSchema as a, type BrandProfile as b, type BrandProfileInput as c, BrandProfileSchema as d, CLASSIC_HOMES_PALETTE as e, type DeckBrand as f, DeckBrandSchema as g, DeckError as h, type DeckInput as i, DeckSchema as j, type DeckSettings as k, DeckSettingsSchema as l, type ImageResolver as m, type ImageValue as n, type InlineImagesResult as o, type ItemValue as p, LAYOUTS as q, LAYOUT_CATEGORIES as r, LAYOUT_CATEGORY_ORDER as s, type LayoutDef as t, type LayoutIssue as u, type LayoutName as v, type RenderResult as w, type ResolvedBrand as x, type SlideInput as y, SlideSchema as z };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as LayoutCategory, B as BrandPalette } from './render-CU0sXO2y.js';
2
- export { a as BrandPaletteSchema, b as BrandProfile, c as BrandProfileInput, d as BrandProfileSchema, C as CLASSIC_HOMES_BRAND, e as CLASSIC_HOMES_PALETTE, D as Deck, f as DeckBrand, g as DeckBrandSchema, h as DeckError, i as DeckInput, j as DeckSchema, k as DeckSettings, l as DeckSettingsSchema, F as FieldMarker, I as ImageInput, m as ImageResolver, n as ImageValue, o as ItemValue, p as LAYOUTS, q as LAYOUT_CATEGORIES, r as LAYOUT_CATEGORY_ORDER, s as LayoutDef, t as LayoutIssue, u as LayoutName, P as PREVIEW_SIZE, R as RenderOptions, v as RenderResult, w as ResolvedBrand, S as SlideData, x as SlideInput, y as SlideSchema, V as ValidateOptions, z as ValidationResult, A as checkPalette, E as collectImageSources, G as extractDeckSource, H as fieldMarkers, J as formatIssues, K as formatPath, M as getLayout, N as renderDeck, O as resolveBrand, Q as validateBrandProfile, T as validateDeck } from './render-CU0sXO2y.js';
1
+ import { L as LayoutCategory, B as BrandPalette } from './images-8N30SsCW.js';
2
+ export { a as BrandPaletteSchema, b as BrandProfile, c as BrandProfileInput, d as BrandProfileSchema, C as CLASSIC_HOMES_BRAND, e as CLASSIC_HOMES_PALETTE, D as Deck, f as DeckBrand, g as DeckBrandSchema, h as DeckError, i as DeckInput, j as DeckSchema, k as DeckSettings, l as DeckSettingsSchema, F as FieldMarker, I as ImageInput, m as ImageResolver, n as ImageValue, o as InlineImagesResult, p as ItemValue, q as LAYOUTS, r as LAYOUT_CATEGORIES, s as LAYOUT_CATEGORY_ORDER, t as LayoutDef, u as LayoutIssue, v as LayoutName, M as MapImagesOptions, P as PREVIEW_SIZE, R as RenderOptions, w as RenderResult, x as ResolvedBrand, S as SlideData, y as SlideInput, z as SlideSchema, V as ValidateOptions, A as ValidationResult, E as checkPalette, G as collectImageSources, H as externalizeDeckImages, J as extractDeckSource, K as fieldMarkers, N as formatIssues, O as formatPath, Q as getLayout, T as inlineDeckImages, U as mapDeckImages, W as renderDeck, X as resolveBrand, Y as validateBrandProfile, Z as validateDeck } from './images-8N30SsCW.js';
3
3
  import * as zod_to_json_schema from 'zod-to-json-schema';
4
4
  import { z } from 'zod';
5
5
 
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  deckJsonSchema,
4
4
  describeLayout,
5
5
  listLayouts
6
- } from "./chunk-JFUJ47TP.js";
6
+ } from "./chunk-TEE3OGRQ.js";
7
7
  import {
8
8
  BrandPaletteSchema,
9
9
  BrandProfileSchema,
@@ -34,6 +34,7 @@ import {
34
34
  describeChart,
35
35
  ensureSlideIds,
36
36
  escapeHtml,
37
+ externalizeDeckImages,
37
38
  extractDeckSource,
38
39
  fieldMarkers,
39
40
  findSlide,
@@ -42,10 +43,12 @@ import {
42
43
  formatValue,
43
44
  getLayout,
44
45
  inline,
46
+ inlineDeckImages,
45
47
  isPlaceholderImage,
46
48
  layoutEditorInfo,
47
49
  layoutGalleryDeck,
48
50
  logoLockup,
51
+ mapDeckImages,
49
52
  moveSlide,
50
53
  newSlideId,
51
54
  outline,
@@ -62,7 +65,7 @@ import {
62
65
  validateBrandProfile,
63
66
  validateDeck,
64
67
  videoEmbedUrl
65
- } from "./chunk-PTTLL6PQ.js";
68
+ } from "./chunk-OUAMH2TP.js";
66
69
 
67
70
  // src/generated/runtime-hash.ts
68
71
  var RUNTIME_SCRIPT_HASH = "sha256-gElvQAb4VPvKu4UeyJRVKuoHYNp8NGhXwx2EhL7NB88=";
@@ -100,6 +103,7 @@ export {
100
103
  describeLayout,
101
104
  ensureSlideIds,
102
105
  escapeHtml,
106
+ externalizeDeckImages,
103
107
  extractDeckSource,
104
108
  fieldMarkers,
105
109
  findSlide,
@@ -108,11 +112,13 @@ export {
108
112
  formatValue,
109
113
  getLayout,
110
114
  inline,
115
+ inlineDeckImages,
111
116
  isPlaceholderImage,
112
117
  layoutEditorInfo,
113
118
  layoutGalleryDeck,
114
119
  listLayouts,
115
120
  logoLockup,
121
+ mapDeckImages,
116
122
  moveSlide,
117
123
  newSlideId,
118
124
  outline,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generated/runtime-hash.ts"],"sourcesContent":["// Generated by scripts/embed-fonts.ts. Do not edit.\n/** CSP source for the inline presenter runtime: script-src '<hash>'. */\nexport const RUNTIME_SCRIPT_HASH = 'sha256-gElvQAb4VPvKu4UeyJRVKuoHYNp8NGhXwx2EhL7NB88=';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEO,IAAM,sBAAsB;","names":[]}
1
+ {"version":3,"sources":["../src/generated/runtime-hash.ts"],"sourcesContent":["// Generated by scripts/embed-fonts.ts. Do not edit.\n/** CSP source for the inline presenter runtime: script-src '<hash>'. */\nexport const RUNTIME_SCRIPT_HASH = 'sha256-gElvQAb4VPvKu4UeyJRVKuoHYNp8NGhXwx2EhL7NB88=';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEO,IAAM,sBAAsB;","names":[]}
@@ -2,13 +2,13 @@ import {
2
2
  AUTHORING_GUIDE,
3
3
  describeLayout,
4
4
  listLayouts
5
- } from "./chunk-JFUJ47TP.js";
5
+ } from "./chunk-TEE3OGRQ.js";
6
6
  import {
7
7
  buildDeckFile,
8
8
  confinedPath,
9
9
  loadDeckFile,
10
10
  saveDeckFile
11
- } from "./chunk-L7G3VH7K.js";
11
+ } from "./chunk-GX6P2SE6.js";
12
12
  import {
13
13
  addSlide,
14
14
  formatIssues,
@@ -18,7 +18,7 @@ import {
18
18
  stringifyDeck,
19
19
  updateSlide,
20
20
  validateDeck
21
- } from "./chunk-PTTLL6PQ.js";
21
+ } from "./chunk-OUAMH2TP.js";
22
22
 
23
23
  // src/node/mcp.ts
24
24
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -320,4 +320,4 @@ export {
320
320
  createSlidesMcpServer,
321
321
  runStdio
322
322
  };
323
- //# sourceMappingURL=mcp-UX32IP2J.js.map
323
+ //# sourceMappingURL=mcp-3WAVCLQ2.js.map
package/dist/node.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RenderOptions, v as RenderResult, m as ImageResolver } from './render-CU0sXO2y.js';
2
- export { N as renderDeck } from './render-CU0sXO2y.js';
1
+ import { R as RenderOptions, w as RenderResult, o as InlineImagesResult, m as ImageResolver } from './images-8N30SsCW.js';
2
+ export { W as renderDeck } from './images-8N30SsCW.js';
3
3
  import 'zod';
4
4
 
5
5
  /** Largest single image we inline without warning. Big photos bloat the deck file. */
@@ -23,5 +23,17 @@ declare function buildDeckFile(path: string, options?: BuildOptions): Promise<Re
23
23
  out: string;
24
24
  bytes: number;
25
25
  }>;
26
+ interface ExportOptions extends FileResolverOptions {
27
+ out?: string;
28
+ }
29
+ /**
30
+ * Writes a portable copy of a deck file: JSON with every image inlined as a
31
+ * data URI, ready to import into a hosted deck editor. Defaults to
32
+ * `<name>.deck.json` next to the source.
33
+ */
34
+ declare function exportDeckFile(path: string, options?: ExportOptions): Promise<InlineImagesResult & {
35
+ out: string;
36
+ bytes: number;
37
+ }>;
26
38
 
27
- export { type BuildOptions, type FileResolverOptions, IMAGE_WARN_BYTES, buildDeckFile, fileImageResolver, loadDeckFile, saveDeckFile };
39
+ export { type BuildOptions, type ExportOptions, type FileResolverOptions, IMAGE_WARN_BYTES, buildDeckFile, exportDeckFile, fileImageResolver, loadDeckFile, saveDeckFile };
package/dist/node.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import {
2
2
  IMAGE_WARN_BYTES,
3
3
  buildDeckFile,
4
+ exportDeckFile,
4
5
  fileImageResolver,
5
6
  loadDeckFile,
6
7
  saveDeckFile
7
- } from "./chunk-L7G3VH7K.js";
8
+ } from "./chunk-GX6P2SE6.js";
8
9
  import {
9
10
  renderDeck
10
- } from "./chunk-PTTLL6PQ.js";
11
+ } from "./chunk-OUAMH2TP.js";
11
12
  export {
12
13
  IMAGE_WARN_BYTES,
13
14
  buildDeckFile,
15
+ exportDeckFile,
14
16
  fileImageResolver,
15
17
  loadDeckFile,
16
18
  renderDeck,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/slides",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Branded Classic Homes presentations from structured deck files: schema, layouts, renderer, CLI and MCP server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/node/index.ts","../src/node/paths.ts"],"sourcesContent":["/** Node-only helpers: read deck files, inline local images, write HTML. */\nimport { readFile, stat, writeFile } from 'node:fs/promises';\nimport { dirname, extname, isAbsolute, join, resolve } from 'node:path';\nimport { confinedPath } from './paths.js';\nimport { parseDeckSource, stringifyDeck } from '../parse.js';\nimport {\n renderDeck,\n type ImageResolver,\n type RenderOptions,\n type RenderResult,\n} from '../render.js';\n\nconst MIME: Record<string, string> = {\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.png': 'image/png',\n '.webp': 'image/webp',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.avif': 'image/avif',\n};\n\n/** Largest single image we inline without warning. Big photos bloat the deck file. */\nexport const IMAGE_WARN_BYTES = 800 * 1024;\n\nexport interface FileResolverOptions {\n /** Restrict local image reads and build paths to this root (used by the MCP server). */\n rootDir?: string;\n /** Fetch https images and inline them so the deck works offline. Default true. */\n inlineRemote?: boolean;\n onWarning?: (message: string) => void;\n}\n\n/** Resolves image paths relative to the deck file and inlines them as data URIs. */\nexport function fileImageResolver(\n baseDir: string,\n options: FileResolverOptions = {}\n): ImageResolver {\n const warn = options.onWarning ?? (() => {});\n return async (src) => {\n if (src.startsWith('data:')) return src;\n if (/^https?:/.test(src)) {\n if (options.inlineRemote === false) return src;\n const res = await fetch(src);\n if (!res.ok) throw new Error(`Image ${src} returned HTTP ${res.status}`);\n const type = res.headers.get('content-type') ?? 'application/octet-stream';\n const buf = Buffer.from(await res.arrayBuffer());\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider a smaller copy.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n }\n const candidate = isAbsolute(src) ? src : join(baseDir, src);\n const path = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n const type = MIME[extname(path).toLowerCase()];\n if (!type)\n throw new Error(`Unsupported image type for \"${src}\". Use jpg, png, webp, gif, svg or avif.`);\n let buf: Buffer;\n try {\n buf = await readFile(path);\n } catch {\n throw new Error(`Image not found: \"${src}\" (looked in ${path})`);\n }\n if (buf.length > IMAGE_WARN_BYTES)\n warn(`${src} is ${(buf.length / 1024).toFixed(0)} KB; consider resizing to ~2000px wide.`);\n return `data:${type};base64,${buf.toString('base64')}`;\n };\n}\n\nexport async function loadDeckFile(path: string): Promise<unknown> {\n return parseDeckSource(await readFile(path, 'utf8'));\n}\n\nexport async function saveDeckFile(path: string, deck: unknown): Promise<void> {\n const text = path.endsWith('.json') ? JSON.stringify(deck, null, 2) + '\\n' : stringifyDeck(deck);\n await writeFile(path, text);\n}\n\nexport interface BuildOptions extends Omit<RenderOptions, 'resolveImage'>, FileResolverOptions {\n out?: string;\n}\n\n/** Renders a deck file to a self-contained HTML file next to it (or at `out`). */\nexport async function buildDeckFile(\n path: string,\n options: BuildOptions = {}\n): Promise<RenderResult & { out: string; bytes: number }> {\n const abs = options.rootDir ? confinedPath(options.rootDir, resolve(path)) : resolve(path);\n const deck = await loadDeckFile(abs);\n const result = await renderDeck(deck, {\n ...options,\n resolveImage: fileImageResolver(dirname(abs), options),\n });\n const candidate = options.out\n ? resolve(options.out)\n : abs.replace(/\\.(ya?ml|json)$/i, '') + '.html';\n const out = options.rootDir ? confinedPath(options.rootDir, candidate) : candidate;\n await writeFile(out, result.html);\n return { ...result, out, bytes: (await stat(out)).size };\n}\n\nexport { renderDeck };\n","/** Canonical containment for existing files and new output files. */\nimport { lstatSync, realpathSync } from 'node:fs';\nimport { dirname, relative, resolve, sep } from 'node:path';\n\nexport function confinedPath(root: string, path: string): string {\n const canonicalRoot = realpathSync(root);\n const absolute = resolve(path);\n let ancestor = absolute;\n for (;;) {\n try {\n const canonical = resolve(realpathSync(ancestor), relative(ancestor, absolute));\n if (canonical !== canonicalRoot && !canonical.startsWith(canonicalRoot + sep)) {\n throw new Error(`Path \"${path}\" is outside the deck root ${root}.`);\n }\n return canonical;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT' || ancestor === dirname(ancestor))\n throw error;\n if (lstatSync(ancestor, { throwIfNoEntry: false })?.isSymbolicLink()) {\n throw new Error(`Cannot follow dangling symlink \"${ancestor}\".`);\n }\n ancestor = dirname(ancestor);\n }\n }\n}\n"],"mappings":"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","names":["dirname","resolve","type","buf","resolve","dirname"]}