ppt-dsl-renderer 1.1.0 → 1.1.1

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.
@@ -6,7 +6,13 @@ Agent 默认使用统一 CLI。命令、参数、输入和返回协议以 [Agent
6
6
 
7
7
  ### 部署与浏览器
8
8
 
9
- 需要 Node.js 22+、包含 `export-pptx` 的构建包,以及后端机器/容器中已安装的 Chrome/Chromium、系统库和字体。`playwright-core@1.62.1` 是包的 Node 运行依赖,不能作为测试依赖删除;它不下载浏览器,也不要求 Python 安装 `playwright`。
9
+ 需要 Node.js 22+、包含 `export-pptx` 的构建包,以及后端机器/容器中已安装的 Chrome/Chromium、系统库和字体。调用方必须显式安装可选 peer dependency `playwright-core@1.62.1`;主包不会自动安装浏览器驱动。它不下载浏览器,也不要求 Python 安装 `playwright`。
10
+
11
+ ```bash
12
+ yarn add ppt-dsl-renderer playwright-core@1.62.1
13
+ ```
14
+
15
+ 只使用 HTML、Vue 或浏览器端 PPTX API 的项目无需安装 `playwright-core`。
10
16
 
11
17
  默认使用 Google Chrome。若后端通过自己的 Playwright 安装了 Chromium,可将其实际可执行文件路径传给 `--browser-executable`,并确保 CLI 进程有执行权限。**每次 CLI 调用仍会启动一个新实例,不复用已有浏览器进程或 Playwright 对象;当前没有 CDP/远程连接参数。**
12
18
 
@@ -58,7 +64,7 @@ pptx_bytes = pptx_path.read_bytes()
58
64
 
59
65
  ## CLI 实现范围
60
66
 
61
- `src/cli.js` 新增 `export-pptx` 及其参数;`src/pptx-cli.js` 包装浏览器生命周期、二进制传输和文件保存。`package.json`/`yarn.lock` 声明 `playwright-core` 运行依赖,`vite.cli.config.js` 将驱动作为外部依赖,发布入口仍为 `dist/cli.js`。
67
+ `src/cli.js` 提供 `export-pptx` 及其参数;`src/pptx-cli.js` 包装浏览器生命周期、二进制传输和文件保存。`package.json` `playwright-core` 声明为可选 peer dependency,并保留为包自身测试所需的 dev dependency;`vite.cli.config.js` 将驱动作为外部依赖,发布入口仍为 `dist/cli.js`。
62
68
 
63
69
  CLI 复用原 `dist/pptx/exporter.js`,没有修改 DSL、Vue/HTML 渲染或 PPTX 转换算法,没有增加 demo 接口。原有 `render` 和浏览器 `exportToPptx` API 保持兼容。
64
70
 
package/AGENT_USAGE.md CHANGED
@@ -5,7 +5,8 @@ Agent 先读取安装包内的 `node_modules/ppt-dsl-renderer/AGENT_USAGE.md`,
5
5
  ## 运行准备
6
6
 
7
7
  - Node.js 22+。部署时安装 `ppt-dsl-renderer`,使用包含当前 CLI 和渲染/导出产物的构建包;不在每个任务中临时安装包或执行 build。
8
- - HTML 生成不启动浏览器。PPTX 导出依赖随包安装的 `playwright-core@1.62.1`,使用运行机器上已安装的 Google Chrome/Chromium;包不下载浏览器。
8
+ - HTML 生成不启动浏览器。仅使用 `export-pptx` 时,调用方需要显式安装可选 peer dependency `playwright-core@1.62.1`;只使用 HTML、Vue 或浏览器端 PPTX API 时不需要安装。
9
+ - PPTX CLI 使用运行机器上已安装的 Google Chrome/Chromium;`playwright-core` 不会下载浏览器。
9
10
  - 浏览器所需系统库、字体、素材访问权限及可写任务目录由部署方准备。
10
11
  - DSL 是完整 JSON 文件,不是命令行 JSON 字符串或 stdin。字段见 [DSL 文档](docs/DSL.md),示例见 [deck.json](examples/deck.json)。相对路径以终端当前目录为基准,并发任务使用独立的输入/输出路径。
11
12
 
package/README.md CHANGED
@@ -14,6 +14,14 @@
14
14
  yarn add ppt-dsl-renderer
15
15
  ```
16
16
 
17
+ Vue 渲染、Node HTML 生成和浏览器端 PPTX 导出只需安装主包。仅后端使用 `export-pptx` CLI 时,还需由调用方显式安装浏览器驱动:
18
+
19
+ ```bash
20
+ yarn add ppt-dsl-renderer playwright-core@1.62.1
21
+ ```
22
+
23
+ `playwright-core` 是可选 peer dependency,不会随主包自动安装,也不会下载浏览器。CLI 仍要求部署机器已有 Google Chrome/Chromium。
24
+
17
25
  ## CLI 与 Agent 接入
18
26
 
19
27
  ```bash
@@ -22,7 +30,7 @@ ppt-dsl-renderer render ./deck.json --out-dir ./preview
22
30
  ppt-dsl-renderer export-pptx ./deck.json --out ./result.pptx
23
31
  ```
24
32
 
25
- 仅在当前项目安装时,使用 `./node_modules/.bin/ppt-dsl-renderer`。PPTX CLI 通过随包安装的 `playwright-core` 启动已有 Chrome/Chromium,每次创建新实例,不复用后端正在运行的浏览器;不改变原导出算法。
33
+ 仅在当前项目安装时,使用 `./node_modules/.bin/ppt-dsl-renderer`。PPTX CLI 通过调用方显式安装的 `playwright-core@1.62.1` 启动已有 Chrome/Chromium,每次创建新实例,不复用后端正在运行的浏览器;不改变原导出算法。
26
34
 
27
35
  Agent 先读 [统一调用指南](AGENT_USAGE.md),其中集中说明运行前提、全部参数、JSON 返回值及失败处理。后端子进程调用、浏览器生命周期和修改范围见 [PPTX 后端接入](AGENT_PPTX_EXPORT.md)。两份文档随 npm 包发布。
28
36
 
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgs as U } from "node:util";
3
- import { readFile as $, mkdir as T, writeFile as L, rename as B, rm as _ } from "node:fs/promises";
4
- import { resolve as f, dirname as N, join as S } from "node:path";
5
- import { fileURLToPath as k } from "node:url";
2
+ import { parseArgs as j } from "node:util";
3
+ import { readFile as $, mkdir as T, writeFile as D, rename as I, rm as B } from "node:fs/promises";
4
+ import { resolve as w, dirname as P, join as S } from "node:path";
5
+ import { fileURLToPath as L } from "node:url";
6
6
  import { randomUUID as C } from "node:crypto";
7
7
  function J(t) {
8
8
  const e = { status: "loading", errors: [] };
@@ -52,54 +52,54 @@ html, body { width: 100%; height: 100%; overflow: hidden; }
52
52
  </body>
53
53
  </html>`;
54
54
  }
55
- const z = /* @__PURE__ */ new Set(["text", "image", "shape", "line", "chart", "table", "latex", "video", "audio"]), M = /* @__PURE__ */ new Set(["area", "bar", "column", "line", "pie", "radar", "ring", "scatter"]), b = (t) => t !== null && typeof t == "object" && !Array.isArray(t), h = (t) => typeof t == "string", v = (t) => Array.isArray(t) && t.length === 2 && t.every(Number.isFinite);
55
+ const M = /* @__PURE__ */ new Set(["text", "image", "shape", "line", "chart", "table", "latex", "video", "audio"]), z = /* @__PURE__ */ new Set(["area", "bar", "column", "line", "pie", "radar", "ring", "scatter"]), b = (t) => t !== null && typeof t == "object" && !Array.isArray(t), u = (t) => typeof t == "string", E = (t) => Array.isArray(t) && t.length === 2 && t.every(Number.isFinite);
56
56
  function n(t, e) {
57
57
  if (!t)
58
58
  throw new Error(e);
59
59
  }
60
- function E(t, e) {
61
- n(h(t) && /^(https?:\/\/|data:)/i.test(t), `${e} 必须使用 HTTP(S) URL 或 data URL`);
60
+ function N(t, e) {
61
+ n(u(t) && /^(https?:\/\/|data:)/i.test(t), `${e} 必须使用 HTTP(S) URL 或 data URL`);
62
62
  const s = new URL(t);
63
63
  n(["http:", "https:", "data:"].includes(s.protocol), `${e} 资源协议不受支持`);
64
64
  }
65
- function D(t) {
66
- n(b(t), "DSL 顶层必须是对象"), n(h(t.title) && t.title.length > 0, "DSL 缺少 title"), n(Number.isFinite(t.width) && t.width > 0, "DSL width 必须是大于 0 的有限数值"), n(Number.isFinite(t.height) && t.height > 0, "DSL height 必须是大于 0 的有限数值"), n(Array.isArray(t.slides) && t.slides.length > 0, "DSL slides 不能为空"), t.slides.forEach((e, s) => {
65
+ function k(t) {
66
+ n(b(t), "DSL 顶层必须是对象"), n(u(t.title) && t.title.length > 0, "DSL 缺少 title"), n(Number.isFinite(t.width) && t.width > 0, "DSL width 必须是大于 0 的有限数值"), n(Number.isFinite(t.height) && t.height > 0, "DSL height 必须是大于 0 的有限数值"), n(Array.isArray(t.slides) && t.slides.length > 0, "DSL slides 不能为空"), t.slides.forEach((e, s) => {
67
67
  var l, d;
68
68
  const i = `第 ${s + 1} 页`;
69
- n(b(e) && h(e.id) && e.id && Array.isArray(e.elements), `${i}结构无效`), ((l = e.background) == null ? void 0 : l.type) === "image" && E((d = e.background.image) == null ? void 0 : d.src, `${i}背景图`);
69
+ n(b(e) && u(e.id) && e.id && Array.isArray(e.elements), `${i}结构无效`), ((l = e.background) == null ? void 0 : l.type) === "image" && N((d = e.background.image) == null ? void 0 : d.src, `${i}背景图`);
70
70
  const a = /* @__PURE__ */ new Set();
71
71
  e.elements.forEach((r) => {
72
72
  var g;
73
- n(b(r) && h(r.id) && r.id && z.has(r.type), `${i}存在无效元素`);
73
+ n(b(r) && u(r.id) && r.id && M.has(r.type), `${i}存在无效元素`);
74
74
  const p = `${i}元素 ${r.id}`;
75
75
  n(!a.has(r.id), `${p} ID 重复`), a.add(r.id), n([r.left, r.top].every(Number.isFinite), `${p}缺少有效坐标`);
76
76
  for (const o of ["width", "height", "rotate", "opacity"])
77
77
  r[o] !== void 0 && n(Number.isFinite(r[o]), `${p}.${o} 必须是有限数值`);
78
78
  switch (r.type) {
79
79
  case "text":
80
- n(h(r.content), `${p}.content 必须是字符串`);
80
+ n(u(r.content), `${p}.content 必须是字符串`);
81
81
  break;
82
82
  case "image":
83
83
  case "audio":
84
84
  case "video":
85
- E(r.src, `${p}.src`), r.poster !== void 0 && E(r.poster, `${p}.poster`);
85
+ N(r.src, `${p}.src`), r.poster !== void 0 && N(r.poster, `${p}.poster`);
86
86
  break;
87
87
  case "shape":
88
- n(h(r.path) && v(r.viewBox), `${p}缺少 path 或有效 viewBox`), r.text !== void 0 && n(h((g = r.text) == null ? void 0 : g.content), `${p}.text.content 必须是字符串`);
88
+ n(u(r.path) && E(r.viewBox), `${p}缺少 path 或有效 viewBox`), r.text !== void 0 && n(u((g = r.text) == null ? void 0 : g.content), `${p}.text.content 必须是字符串`);
89
89
  break;
90
90
  case "line":
91
- n(v(r.start) && v(r.end) && h(r.color), `${p}缺少 start、end 或 color`);
91
+ n(E(r.start) && E(r.end) && u(r.color), `${p}缺少 start、end 或 color`);
92
92
  break;
93
93
  case "chart": {
94
94
  const o = r.data;
95
- n(M.has(r.chartType), `${p}.chartType 不受支持`), n(b(o) && Array.isArray(o.labels) && o.labels.every(h) && Array.isArray(o.legends) && o.legends.every(h) && Array.isArray(o.series) && o.series.every((c) => Array.isArray(c) && c.every(Number.isFinite)), `${p}.data 结构无效`);
95
+ n(z.has(r.chartType), `${p}.chartType 不受支持`), n(b(o) && Array.isArray(o.labels) && o.labels.every(u) && Array.isArray(o.legends) && o.legends.every(u) && Array.isArray(o.series) && o.series.every((h) => Array.isArray(h) && h.every(Number.isFinite)), `${p}.data 结构无效`);
96
96
  break;
97
97
  }
98
98
  case "table":
99
- n(Array.isArray(r.data) && r.data.every((o) => Array.isArray(o) && o.every((c) => b(c) && h(c.id) && c.id && h(c.text))), `${p}.data 结构无效`);
99
+ n(Array.isArray(r.data) && r.data.every((o) => Array.isArray(o) && o.every((h) => b(h) && u(h.id) && h.id && u(h.text))), `${p}.data 结构无效`);
100
100
  break;
101
101
  case "latex":
102
- n(h(r.latex), `${p}.latex 必须是字符串`);
102
+ n(u(r.latex), `${p}.latex 必须是字符串`);
103
103
  break;
104
104
  }
105
105
  });
@@ -108,9 +108,9 @@ function D(t) {
108
108
  function F(t, e) {
109
109
  return e === void 0 ? Array.from({ length: t }, (s, i) => i + 1) : (n(Array.isArray(e) && e.length > 0, "pages 必须是非空页码数组"), n(e.every((s) => Number.isInteger(s) && s >= 1 && s <= t), `页码必须是 1 到 ${t} 的整数`), [...new Set(e)].sort((s, i) => s - i));
110
110
  }
111
- const P = f(N(k(import.meta.url)), "..");
111
+ const v = w(P(L(import.meta.url)), "..");
112
112
  async function q(t, e = {}) {
113
- D(t);
113
+ k(t);
114
114
  const i = F(t.slides.length, e.pages).map((d) => ({
115
115
  pageNumber: d,
116
116
  slideId: t.slides[d - 1].id,
@@ -127,8 +127,8 @@ async function q(t, e = {}) {
127
127
  let a, l;
128
128
  try {
129
129
  [a, l] = await Promise.all([
130
- $(S(P, "dist/browser/renderer.js"), "utf8"),
131
- $(S(P, "dist/browser/renderer.css"), "utf8")
130
+ $(S(v, "dist/browser/renderer.js"), "utf8"),
131
+ $(S(v, "dist/browser/renderer.css"), "utf8")
132
132
  ]);
133
133
  } catch (d) {
134
134
  throw d.code !== "ENOENT" ? d : new Error("缺少预编译浏览器资源。源码仓库请先显式执行 yarn build;npm 安装包应包含 dist/browser。", { cause: d });
@@ -138,43 +138,45 @@ async function q(t, e = {}) {
138
138
  html: X(d, a, l)
139
139
  }));
140
140
  }
141
- const V = f(N(k(import.meta.url)), "..");
141
+ const V = w(P(L(import.meta.url)), "..");
142
142
  async function W(t, e, { pages: s, executablePath: i, timeout: a = 12e4 } = {}) {
143
- D(t);
144
- const l = F(t.slides.length, s), d = await $(S(V, "dist/pptx/exporter.js"), "utf8").catch((u) => {
145
- throw u.code !== "ENOENT" ? u : new Error("安装包缺少 PPTX 运行产物,请使用完整构建的 ppt-dsl-renderer 包。", { cause: u });
146
- }), { chromium: r } = await import("playwright-core"), p = await r.launch({ headless: !0, ...i ? { executablePath: i } : { channel: "chrome" }, timeout: a });
143
+ k(t);
144
+ const l = F(t.slides.length, s), d = await $(S(V, "dist/pptx/exporter.js"), "utf8").catch((c) => {
145
+ throw c.code !== "ENOENT" ? c : new Error("安装包缺少 PPTX 运行产物,请使用完整构建的 ppt-dsl-renderer 包。", { cause: c });
146
+ }), { chromium: r } = await import("playwright-core").catch((c) => {
147
+ throw c.code !== "ERR_MODULE_NOT_FOUND" ? c : new Error("PPTX 导出需要显式安装 playwright-core@1.62.1。", { cause: c });
148
+ }), p = await r.launch({ headless: !0, ...i ? { executablePath: i } : { channel: "chrome" }, timeout: a });
147
149
  let g, o;
148
150
  try {
149
- const u = (async () => {
150
- const w = await p.newPage();
151
- w.on("console", (y) => console.error(y.text())), w.on("pageerror", (y) => console.error(y.message)), await w.addScriptTag({ content: d });
152
- const A = await w.evaluate(async ({ document: y, pages: j }) => {
153
- const I = await window.exportToPptx(y, { pages: j });
154
- return new Promise((O, R) => {
151
+ const c = (async () => {
152
+ const f = await p.newPage();
153
+ f.on("console", (y) => console.error(y.text())), f.on("pageerror", (y) => console.error(y.message)), await f.addScriptTag({ content: d });
154
+ const A = await f.evaluate(async ({ document: y, pages: O }) => {
155
+ const R = await window.exportToPptx(y, { pages: O });
156
+ return new Promise((U, _) => {
155
157
  const m = new FileReader();
156
- m.onload = () => O(m.result.split(",", 2)[1]), m.onerror = () => R(m.error), m.readAsDataURL(new Blob([I]));
158
+ m.onload = () => U(m.result.split(",", 2)[1]), m.onerror = () => _(m.error), m.readAsDataURL(new Blob([R]));
157
159
  });
158
160
  }, { document: t, pages: l });
159
161
  return Buffer.from(A, "base64");
160
162
  })();
161
163
  o = await Promise.race([
162
- u,
163
- new Promise((w, A) => {
164
+ c,
165
+ new Promise((f, A) => {
164
166
  g = setTimeout(() => A(new Error(`PPTX 导出超时(${a}ms)`)), a);
165
167
  })
166
168
  ]);
167
169
  } finally {
168
170
  clearTimeout(g), await p.close();
169
171
  }
170
- await T(N(e), { recursive: !0 });
171
- const c = `${e}.${C()}.tmp`;
172
+ await T(P(e), { recursive: !0 });
173
+ const h = `${e}.${C()}.tmp`;
172
174
  try {
173
- await L(c, o, { flag: "wx" }), await B(c, e);
175
+ await D(h, o, { flag: "wx" }), await I(h, e);
174
176
  } finally {
175
- await _(c, { force: !0 });
177
+ await B(h, { force: !0 });
176
178
  }
177
- return { file: e, pages: l.map((u) => ({ pageNumber: u, slideId: t.slides[u - 1].id })), bytes: o.length };
179
+ return { file: e, pages: l.map((c) => ({ pageNumber: c, slideId: t.slides[c - 1].id })), bytes: o.length };
178
180
  }
179
181
  const x = `Usage:
180
182
  ppt-dsl-renderer render <deck.json> --out-dir <directory> [--pages 1,3]
@@ -190,7 +192,7 @@ render generates HTML without launching a browser.
190
192
  export-pptx requires Chrome or Chromium installed at deployment time; it never downloads a browser.`;
191
193
  async function G() {
192
194
  var g;
193
- const { values: t, positionals: e } = U({
195
+ const { values: t, positionals: e } = j({
194
196
  allowPositionals: !0,
195
197
  options: {
196
198
  "out-dir": { type: "string" },
@@ -217,24 +219,24 @@ async function G() {
217
219
  const a = (g = t.pages) == null ? void 0 : g.split(",").map(Number);
218
220
  if (t.timeout !== void 0 && (!/^\d+$/.test(t.timeout) || !Number.isSafeInteger(Number(t.timeout)) || Number(t.timeout) <= 0))
219
221
  throw new Error("--timeout 必须是正整数毫秒");
220
- if (s === "export-pptx" && f(i) === f(t.out))
222
+ if (s === "export-pptx" && w(i) === w(t.out))
221
223
  throw new Error("--out 不能覆盖输入 DSL 文件");
222
- const l = JSON.parse(await $(f(i), "utf8"));
224
+ const l = JSON.parse(await $(w(i), "utf8"));
223
225
  if (s === "export-pptx") {
224
- const o = await W(l, f(t.out), {
226
+ const o = await W(l, w(t.out), {
225
227
  pages: a,
226
- executablePath: t["browser-executable"] ? f(t["browser-executable"]) : void 0,
228
+ executablePath: t["browser-executable"] ? w(t["browser-executable"]) : void 0,
227
229
  timeout: t.timeout === void 0 ? void 0 : Number(t.timeout)
228
230
  });
229
231
  console.log(JSON.stringify(o, null, 2));
230
232
  return;
231
233
  }
232
- const d = await q(l, { pages: a }), r = f(t["out-dir"]);
234
+ const d = await q(l, { pages: a }), r = w(t["out-dir"]);
233
235
  await T(r, { recursive: !0 });
234
236
  const p = [];
235
- for (const { html: o, ...c } of d) {
236
- const u = S(r, `slide-${String(c.pageNumber).padStart(3, "0")}.html`);
237
- await L(u, o, "utf8"), p.push({ ...c, file: u });
237
+ for (const { html: o, ...h } of d) {
238
+ const c = S(r, `slide-${String(h.pageNumber).padStart(3, "0")}.html`);
239
+ await D(c, o, "utf8"), p.push({ ...h, file: c });
238
240
  }
239
241
  console.log(JSON.stringify({ pages: p }, null, 2));
240
242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ppt-dsl-renderer",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Render PPT DSL with Vue components or standalone, agent-friendly HTML slides.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,15 +52,18 @@
52
52
  "dependencies": {
53
53
  "dompurify": "3.3.1",
54
54
  "echarts": "6.1.0",
55
- "playwright-core": "1.62.1",
56
55
  "pptxgenjs": "3.12.0",
57
56
  "svg-pathdata": "7.2.0",
58
57
  "tinycolor2": "1.6.0"
59
58
  },
60
59
  "peerDependencies": {
60
+ "playwright-core": "1.62.1",
61
61
  "vue": "^3.4.21"
62
62
  },
63
63
  "peerDependenciesMeta": {
64
+ "playwright-core": {
65
+ "optional": true
66
+ },
64
67
  "vue": {
65
68
  "optional": true
66
69
  }
@@ -70,6 +73,7 @@
70
73
  "@vue/compiler-sfc": "3.4.21",
71
74
  "jsdom": "26.1.0",
72
75
  "jszip": "3.10.1",
76
+ "playwright-core": "1.62.1",
73
77
  "sass": "1.62.1",
74
78
  "typescript": "5.0.4",
75
79
  "vite": "4.5.9",