dsh-ppt 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@
4
4
 
5
5
  [历史英文记录](docs/CHANGELOG.en.md)
6
6
 
7
+ ## 0.10.0 (2026-10-15)
8
+
9
+ 实际操作、最终 PPTX 渲染与未测范围见 [验收记录](docs/validation-0.10.0.md)。
10
+
11
+ - 设置页可复制、删除当前页,复制时保留图片、原生图表与备注;副本拥有独立页面 ID。
12
+ - `ppt_edit` 增加 `duplicate` / `remove`,支持与文字修改、排序合并成一次原子修改,整步可撤销。
13
+ - 保留至少一页并限制 120 页;未保存输入或修订冲突时阻止结构修改,避免覆盖正在编辑的内容。
14
+
7
15
  ## 0.9.0 (2026-10-05)
8
16
 
9
17
  - 增加成品 ZIP 下载:一次交付 HTML、可编辑 PPTX、工程 JSON 和使用说明;只打包本项目登记的当前修订,未保存或产物缺失时阻止交付。
package/README.en.md CHANGED
@@ -8,10 +8,12 @@ Turn source material into an HTML slideshow, editable PPTX and a reusable projec
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/dsh-ppt)](https://www.npmjs.com/package/dsh-ppt) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-ppt-downloads.svg)](https://www.npmjs.com/package/dsh-ppt)
10
10
 
11
+ Feedback and contributions are welcome: report [issues](https://github.com/STARDUSTLC666/dsh-ppt/issues) or submit [pull requests](https://github.com/STARDUSTLC666/dsh-ppt/pulls).
12
+
11
13
  ## What it does
12
14
 
13
15
  - Create branded decks with text, images, tables and native charts.
14
- - Edit text and images in Settings, reorder pages and undo changes.
16
+ - Edit text and images in Settings, reorder, duplicate or delete slides, and undo changes.
15
17
  - Check quality and optionally render the final PPTX as images or PDF.
16
18
 
17
19
  ## Install
package/README.md CHANGED
@@ -8,10 +8,12 @@
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/dsh-ppt)](https://www.npmjs.com/package/dsh-ppt) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-ppt-downloads.svg)](https://www.npmjs.com/package/dsh-ppt)
10
10
 
11
+ 欢迎使用,遇到问题或有改进建议,请提交 [issues](https://github.com/STARDUSTLC666/dsh-ppt/issues) 和 [PR](https://github.com/STARDUSTLC666/dsh-ppt/pulls)。
12
+
11
13
  ## 功能
12
14
 
13
15
  - 生成图文、表格、原生图表和品牌化演示文稿。
14
- - 在设置页编辑文字和图片、调整页面顺序,并支持撤销。
16
+ - 在设置页编辑文字和图片、调整顺序、复制或删除页面,并支持撤销。
15
17
  - 提供质量检查与可选的最终 PPTX 图片 / PDF 渲染。
16
18
 
17
19
  ## 安装
package/docs/USAGE.en.md CHANGED
@@ -84,7 +84,7 @@ Existing artifacts are not overwritten by default: if any member of the trio alr
84
84
 
85
85
  ### Lightweight Settings editor (0.8.0)
86
86
 
87
- Open Settings → Presentations and choose a project registered by `ppt_create`, `ppt_edit` or `ppt_check`. Select a page to edit text, bullets and speaker notes; clicking text in the HTML preview focuses its field. Upload PNG/JPEG images, reorder pages and undo changes. Saving regenerates HTML, PPTX and JSON together.
87
+ Open Settings → Presentations and choose a project registered by `ppt_create`, `ppt_edit` or `ppt_check`. Select a page to edit text, bullets and speaker notes; clicking text in the HTML preview focuses its field. Upload PNG/JPEG images, reorder, duplicate or delete slides and undo changes. Copies preserve images, charts and notes. The last slide cannot be deleted; the limit is 120 slides. Save or cancel pending edits before changing slide structure. Saving regenerates HTML, PPTX and JSON together.
88
88
 
89
89
  Save or cancel pending changes before switching pages, reordering, downloading or rendering. A stale revision cannot overwrite another writer's changes. Use “Load latest, keep my input”, review overlapping fields, then explicitly save. The HTML preview and actual PPTX rendering are separate; edits invalidate previous renders. Review every rendered page and font diagnostics before downloading the latest files. The editor works with this plugin's own JSON projects; it does not import arbitrary external PPTX files, and ordinary generation/editing does not need the optional renderer.
90
90
 
@@ -98,6 +98,8 @@ A chart accepts `chart: {kind:"column"|"bar"|"line"|"pie", categories, series:[{
98
98
 
99
99
  Edit example: `{deckPath:"report.json",expectedRevision:0,edits:[{slide:3,patch:{title:"A clearer conclusion",bullets:["Evidence","Next action"]}}]}`. Use `ppt_check` to obtain stable IDs and the revision; `ppt_undo` restores the latest edit including assets/brand. Keeps up to 20 edits and rejects stale revisions. The artifact trio is regenerated atomically with rollback on commit failure.
100
100
 
101
+ Structure example: `{deckPath:"report.json",expectedRevision:0,duplicate:[2],remove:[3]}`. References use the original 1-based slide numbers or stable IDs. Copies follow the source slide with new IDs; existing slides keep theirs. A slide cannot be copied and removed in the same request. Keep 1–120 slides. Text edits, reordering and structural changes can share one undo step.
102
+
101
103
  `deliveryStatus: draft` means unfilled slots; `ready-for-review` means review can start. Static estimates, rendering and visual inspection are recorded separately. Generation/rendering never automatically certifies visual quality. Inspect HTML and every page image before delivery.
102
104
 
103
105
  ## Optional PNG/PDF renderer
package/docs/USAGE.md CHANGED
@@ -81,7 +81,7 @@ node <skill-dir>/scripts/build-deck.mjs \
81
81
 
82
82
  ### 设置页轻量编辑(0.8.0)
83
83
 
84
- 打开「设置 → 演示文稿」,选择由 `ppt_create`、`ppt_edit` 或 `ppt_check` 登记的本插件工程。点击页码,修改标题、正文、要点和演讲备注;也可点击 HTML 预览中的文字定位字段。上传 PNG/JPEG 会嵌入工程并同步生成 PPTX。页面上移/下移与文字修改均可撤销。
84
+ 打开「设置 → 演示文稿」,选择由 `ppt_create`、`ppt_edit` 或 `ppt_check` 登记的本插件工程。点击页码,修改标题、正文、要点和演讲备注;也可点击 HTML 预览中的文字定位字段。上传 PNG/JPEG 会嵌入工程并同步生成 PPTX。页面可上移/下移、复制、删除,均支持撤销。复制会保留图片、图表和备注;最后一页不能删除,最多 120 页。有未保存输入时,先保存或取消再操作页面结构。
85
85
 
86
86
  保存会同时更新 HTML、PPTX 和 JSON。未保存时先保存或取消,再切换页面、排序、下载或渲染。若助手同时修改了工程,旧修订不会覆盖新文件;点击「载入最新版本,保留我的输入」后检查重叠字段,再明确保存。
87
87
 
@@ -107,6 +107,8 @@ node <skill-dir>/scripts/build-deck.mjs \
107
107
 
108
108
  编辑示例:`ppt_edit { deckPath: "report.json", expectedRevision: 0, edits: [{ slide: 3, patch: { title: "新的结论", bullets: ["依据一", "下一步"] } }] }`。先用 `ppt_check` 获取页码/ID/修订号,再修改;`ppt_undo` 撤销最近一次修改,恢复图片、图表和品牌。编辑和撤销更新整套文件,失败会回滚。
109
109
 
110
+ 结构示例:`ppt_edit { deckPath: "report.json", expectedRevision: 0, duplicate: [2], remove: [3] }`。引用按修改前的页码或稳定 ID 解析;副本紧跟原页并获得新 ID,其他页面的 ID 保持不变。同一页不能同时复制和删除,最终须保留 1–120 页;可与文字修改、排序合并为一次可撤销操作。
111
+
110
112
  `deliveryStatus: draft` 表示模板未填完;`ready-for-review` 表示可开始检查。静态估算、PPTX 渲染和视觉检查分开记录。生成或渲染成功不会自动代表视觉验收通过,应查看 HTML 和每页图片,修正问题后交付。
111
113
 
112
114
  ## 可选 PNG / PDF 渲染
@@ -0,0 +1,10 @@
1
+ # 0.10.0 验收记录
2
+
3
+ 2026-10-15,Windows / Node 24.16.0,官方 DSH 0.2.1-alpha.3,隔离配置与合成演示文稿。
4
+
5
+ - 本地测试:119 通过,0 跳过;覆盖复制后的独立 ID、图片/图表/备注保留、组合编辑/排序/复制/删除、非法请求不改变成品、修订冲突和撤销。
6
+ - 真实宿主网页:通过「设置 → 演示文稿」点击图片页,复制、删除、连续撤销;删除至最后一页时按钮不可用;未保存文字时禁止结构操作,取消后恢复。
7
+ - 语言与布局:中文和英文控件均可操作,用户的中文标题与备注保持原文;390px 窄屏完成复制/删除/撤销,工作台无横向溢出;未出现页面异常或浏览器控制台错误。
8
+ - 最终 PPTX:官方 LibreOffice Kit 0.1.5 原生引擎导出三页 PNG 与 PDF;完整性收据通过;人工逐页查看封面、原生柱状图和图片页,无缺字、重叠或裁切,缺失字体列表为空。
9
+
10
+ 网页通过不等于桌面原生能力通过。本次未操作用户桌面窗口,未验证 PowerPoint/WPS 编辑和演示者视图,也未让真实模型自主选择工具;不以这些结果代替相应验收。
package/lib/client.js CHANGED
@@ -25,6 +25,10 @@ const EN = Object.freeze({
25
25
  "内容没有变化。": "No changes to save.",
26
26
  "已保存,HTML、PPTX 和工程文件已同步更新。": "Saved. HTML, PPTX and project files were updated together.",
27
27
  "页面顺序已更新,可撤销。": "Slides reordered. You can undo this change.",
28
+ "复制本页": "Duplicate slide",
29
+ "删除本页": "Delete slide",
30
+ "页面已复制,图片、图表和备注一并保留,可撤销。": "Slide duplicated with its images, charts and notes. You can undo this change.",
31
+ "页面已删除,可撤销。": "Slide deleted. You can undo this change.",
28
32
  "已撤销上次修改。": "Last change undone.",
29
33
  "当前 PPTX 已生成逐页图片,请查看画面。": "Slide images were rendered from the current PPTX. Review each slide.",
30
34
  "正在渲染最终 PPTX,较大文件可能需要几分钟…": "Rendering the final PPTX. Large files may take a few minutes…",
@@ -239,6 +243,12 @@ function useLanguage(locale) {
239
243
  const order = project.slides.map(s => s.id); [order[index], order[index + delta]] = [order[index + delta], order[index]];
240
244
  return perform(signal => action({ operation: 'edit', id: project.id, expectedRevision: project.revision, order }, signal), value => { adopt(value.project, slide.id); setMessage(t("页面顺序已更新,可撤销。")); });
241
245
  }
246
+ function structure(operation) {
247
+ return perform(signal => action({ operation, id: project.id, expectedRevision: project.revision, slide: slide.id }, signal), value => {
248
+ adopt(value.project, value.selected);
249
+ setMessage(operation === 'duplicate' ? t("页面已复制,图片、图表和备注一并保留,可撤销。") : t("页面已删除,可撤销。"));
250
+ });
251
+ }
242
252
  function undo() { return perform(signal => action({ operation: 'undo', id: project.id, expectedRevision: project.revision }, signal), value => { adopt(value.project, selected); setMessage(t("已撤销上次修改。")); }); }
243
253
  function render() {
244
254
  return perform(signal => action({ operation: 'render', id: project.id, expectedRevision: project.revision }, signal), value => { adopt(value.project, selected); setMode('pptx'); setMessage(t("当前 PPTX 已生成逐页图片,请查看画面。")); }, t("正在渲染最终 PPTX,较大文件可能需要几分钟…"));
@@ -274,7 +284,9 @@ function useLanguage(locale) {
274
284
  h('div', { className: 'tools' }, h('button', { disabled: busy || dirty, onClick: () => { setProject(null); refreshList(); } }, t("返回项目")), h('strong', null, project.title), h('span', { className: 'muted' }, project.slides.length + t(" 页 · 修订 ") + project.revision)),
275
285
  h('div', { className: 'layout' },
276
286
  h('div', null, h('h3', null, t("页面")), h('div', { className: 'pages' }, project.slides.map((row, i) => h('button', { key: row.id, 'aria-current': row.id === selected ? 'true' : undefined, disabled: busy || dirty, onClick: () => choose(row) }, (i + 1) + '. ' + (row.title || t("未命名页"))))),
277
- h('div', { className: 'tools' }, h('button', { disabled: busy || dirty || index === 0, onClick: () => move(-1) }, t("上移")), h('button', { disabled: busy || dirty || index === project.slides.length - 1, onClick: () => move(1) }, t("下移")))),
287
+ h('div', { className: 'tools' }, h('button', { disabled: busy || dirty || index === 0, onClick: () => move(-1) }, t("上移")), h('button', { disabled: busy || dirty || index === project.slides.length - 1, onClick: () => move(1) }, t("下移")),
288
+ h('button', { disabled: busy || dirty || project.slides.length >= 120, onClick: () => structure('duplicate') }, t("复制本页")),
289
+ h('button', { disabled: busy || dirty || project.slides.length <= 1, onClick: () => structure('remove') }, t("删除本页")))),
278
290
  h('div', null,
279
291
  h('div', { className: 'tools' }, h('button', { disabled: busy, 'aria-pressed': mode === 'html', onClick: () => setMode('html') }, t("HTML 预览")),
280
292
  h('button', { disabled: busy || !project.render, 'aria-pressed': mode === 'pptx', onClick: () => setMode('pptx') }, t("最终 PPTX 预览")),
package/lib/tools.js CHANGED
@@ -216,6 +216,8 @@ export function buildPptTools(config) {
216
216
  parameters: compileParameters({ ...projectParameters,
217
217
  edits: { type: 'array', items: { type: 'object', properties: { slide: { type: ['integer', 'string'], description: '1-based page number or stable slide ID' }, patch: { type: 'object', properties: slideProperties, additionalProperties: false } }, required: ['slide', 'patch'], additionalProperties: false } },
218
218
  order: { type: 'array', items: { type: 'string' }, description: 'Reorder pages using every stable slide ID exactly once. May be used alone or together with edits; one undo restores the previous content and order.' },
219
+ duplicate: { type: 'array', items: { type: ['integer', 'string'] }, description: 'Copy selected existing slides immediately after their originals, including images/charts/notes. References are 1-based page numbers or stable IDs from before this edit. Copies get new IDs; undo restores the previous deck.' },
220
+ remove: { type: 'array', items: { type: ['integer', 'string'] }, description: 'Remove selected slides by their original 1-based page number or stable ID. At least one page must remain. A slide cannot be copied and removed in the same edit.' },
219
221
  brand: { type: ['object', 'null'], properties: brandProperties, additionalProperties: false, description: 'Merge shared branding. null clears all branding. Empty footer clears footer; logo:null clears logo.' },
220
222
  }),
221
223
  output: { schema: createResultSchema, render: (_args, value) => renderCreate(value) },
@@ -36,6 +36,10 @@ export function englishWorkbenchMessage(message) {
36
36
  return 'This field cannot be edited from the panel.';
37
37
  if (/没有可撤销|无法撤销|历史记录为空/.test(message))
38
38
  return 'No change is available to undo.';
39
+ if (/至少保留一页/.test(message))
40
+ return 'Keep at least one slide in the presentation.';
41
+ if (/工程页数超过 120/.test(message))
42
+ return 'A presentation can contain at most 120 slides.';
39
43
  if (/没有.*图片|超过.*MiB|图片.*无效/.test(message))
40
44
  return 'The image is invalid or too large. Choose another PNG or JPEG image.';
41
45
  return /[\u3400-\u9fff]/.test(message) ? 'The operation failed. Check the project files and DSH logs, then try again.' : message;
package/lib/workbench.js CHANGED
@@ -190,6 +190,17 @@ export class PptWorkbench {
190
190
  await this.remember(result, args);
191
191
  return { ...await this.view(entry.id), changed: result.changed };
192
192
  }
193
+ if (body.operation === 'duplicate' || body.operation === 'remove') {
194
+ if (typeof body.slide !== 'string' || !manifest.slides.some((s) => s.id === body.slide))
195
+ throw new Error('页面不存在');
196
+ const args = { deckPath: entry.path, expectedRevision: body.expectedRevision, signal, [body.operation]: [body.slide] };
197
+ const result = await engine.editDeck(args);
198
+ await this.remember(result, args);
199
+ const view = await this.view(entry.id);
200
+ const originalIndex = manifest.slides.findIndex((s) => s.id === body.slide);
201
+ const selected = view.project.slides[Math.min(originalIndex + (body.operation === 'duplicate' ? 1 : 0), view.project.slides.length - 1)].id;
202
+ return { ...view, selected };
203
+ }
193
204
  if (body.operation === 'undo') {
194
205
  await engine.undoDeck({ deckPath: entry.path, expectedRevision: body.expectedRevision, signal });
195
206
  return this.view(entry.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ppt",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "DSH 演示文稿插件:生成 HTML、可编辑 PPTX 和工程文件,支持图表、轻量编辑、撤销与质量检查。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -35,7 +35,7 @@ allowed-tools: "Bash, Read, Write, Edit"
35
35
  - 动效默认开(页间转场 + 要点逐条入场);严肃/打印场景传 `motion: 'off'`。
36
36
  → 用表格 / 金句 / 备注 / 结构化 slides 前读 `references/syntax.md`。
37
37
  5. **Verify 校验与修正**:调 `ppt_check` 检查具体页;在浏览器操作放映、总览和备注,查看图片/图表。若可选官方引擎可用,调 `ppt_render` 读取最终 PPTX 生成逐页 PNG/PDF,逐页看画面。修正用 `ppt_edit`,以 `expectedRevision` 防冲突;需要时 `ppt_undo`。修改后重新渲染,以收据复核当前文件。文件头/结构通过不等同于视觉验收,静态估算、渲染、视觉检查必须分开说明。
38
- 6. **Deliver 交付**:给用户三个绝对路径,说明 HTML 直接放映(← → 翻页 · F 全屏 · G 总览 · S 备注 · P 打印),PPTX 用 PowerPoint / WPS / Keynote 打开(备注在演示者视图可见,bullets 页放映时逐条点击出现)。DSH 用户也可在「设置 → 演示文稿」修改文字、替换 PNG/JPEG、移动页面、撤销和下载最新文件;已有 JSON 工程先 `ppt_check` 才会出现在项目列表。`ppt_edit.order` 使用每个稳定页面 ID 各一次,与正文修改合并成一次撤销。
38
+ 6. **Deliver 交付**:给用户三个绝对路径,说明 HTML 直接放映(← → 翻页 · F 全屏 · G 总览 · S 备注 · P 打印),PPTX 用 PowerPoint / WPS / Keynote 打开(备注在演示者视图可见,bullets 页放映时逐条点击出现)。DSH 用户也可在「设置 → 演示文稿」修改文字、替换 PNG/JPEG、移动/复制/删除页面、撤销和下载最新文件;已有 JSON 工程先 `ppt_check` 才会出现在项目列表。`ppt_edit.order` 使用每个稳定页面 ID 各一次;`duplicate`、`remove` 使用修改前的页码或稳定 ID,复制保留图片/图表/备注,最终保留 1–120 页。结构与正文修改可合为一次撤销。
39
39
 
40
40
  ## 按需装载索引
41
41
 
@@ -16,7 +16,9 @@ Agent 先准备实际内容,再生成可放映 HTML 与可编辑 PPTX。用户
16
16
 
17
17
  先 `ppt_check {deckPath:"report.json"}` 取得当前revision和页面ID。修改:`ppt_edit {deckPath:"report.json",expectedRevision:0,edits:[{slide:3,patch:{title:"新标题",bullets:["事实一","行动"]}}]}`。slide为1起始页码或ID,其他页保留;重复指定页拒绝。可单独修改brand;brand:null清空品牌,logo:null清空Logo,空footer清空页脚。
18
18
 
19
- 编辑会原子更新整套HTML/PPTX/JSON,失败回滚。历史最多20次。`ppt_undo {deckPath:"report.json",expectedRevision:1}` 恢复上次状态,包含图片/图表/品牌;revision继续增长,旧修改不可覆盖撤销。仅编辑本插件JSON工程,不导入任意外部PPTX进行修改。编辑不能新增/删除页;表格扩大到需要分页时会要求另建文稿。
19
+ 复制与删除:`ppt_edit {deckPath:"report.json",expectedRevision:0,duplicate:[2],remove:[3]}`。两组引用都按修改前的1起始页码或稳定ID解析;副本紧跟原页,获得新ID,并保留图片、图表和备注。同一页不能同时复制和删除,不能重复指定,最终保留1–120页。可与edits和order合为一次修改,先编辑/排序,再复制/删除;order仍需包含每个原始页面ID各一次。
20
+
21
+ 编辑会原子更新整套HTML/PPTX/JSON,失败回滚。历史最多20次。`ppt_undo {deckPath:"report.json",expectedRevision:1}` 恢复上次状态,包含页面结构、图片/图表/品牌;revision继续增长,旧修改不可覆盖撤销。仅编辑本插件JSON工程,不导入任意外部PPTX进行修改。表格扩大到需要分页时会要求另建文稿。
20
22
 
21
23
  ## 检查与渲染
22
24
 
@@ -396,7 +396,7 @@ export async function editDeck(options = {}) {
396
396
  expected(current, options.expectedRevision)
397
397
  const next = clone(current)
398
398
  if (!Array.isArray(options.edits) || !options.edits.length) {
399
- if (options.brand === undefined && options.order === undefined) fail('请提供 edits、order 或 brand')
399
+ if (options.brand === undefined && options.order === undefined && options.duplicate === undefined && options.remove === undefined) fail('请提供 edits、order、duplicate、remove 或 brand')
400
400
  }
401
401
  const used = new Set(), allowed = new Set(['layout', 'title', 'subtitle', 'kicker', 'text', 'bullets', 'rows', 'notes', 'image', 'chart'])
402
402
  for (const edit of options.edits ?? []) {
@@ -425,6 +425,24 @@ export async function editDeck(options = {}) {
425
425
  if (options.order.some(id => !byId.has(id))) fail('order 含有不属于本工程的页面 ID')
426
426
  next.slides = options.order.map(id => byId.get(id))
427
427
  }
428
+ function selectedIds(value, field) {
429
+ if (value === undefined) return new Set()
430
+ if (!Array.isArray(value) || value.length === 0 || value.length > 120) fail(field + ' 需为 1-120 个页码或页面 ID')
431
+ const ids = value.map(ref => {
432
+ const slide = typeof ref === 'number' && Number.isInteger(ref) ? current.slides[ref - 1] : current.slides.find(s => s.id === ref)
433
+ if (!slide) fail(field + ' 找不到页面:' + ref)
434
+ return slide.id
435
+ })
436
+ if (new Set(ids).size !== ids.length) fail(field + ' 不能重复指定同一页')
437
+ return new Set(ids)
438
+ }
439
+ const duplicate = selectedIds(options.duplicate, 'duplicate'), remove = selectedIds(options.remove, 'remove')
440
+ if ([...duplicate].some(id => remove.has(id))) fail('同一页不能同时复制和删除')
441
+ const count = next.slides.length + duplicate.size - remove.size
442
+ if (count < 1) fail('至少保留一页')
443
+ if (count > 120) fail('工程页数超过 120')
444
+ if (duplicate.size || remove.size) next.slides = next.slides.flatMap(slide => remove.has(slide.id) ? [] :
445
+ duplicate.has(slide.id) ? [slide, { ...clone(slide), id: randomUUID() }] : [slide])
428
446
  if (options.brand !== undefined) next.brand = normalizeBrand(options.brand === null ? null : { ...next.brand, ...options.brand,
429
447
  ...(options.brand?.logo === null ? { logo: undefined, logoAssetId: undefined } : {}) }, next.assets, resolve(options.cwd || '.'))
430
448
  if (JSON.stringify(snapshot(next)) === JSON.stringify(snapshot(current))) return { ...result(current, pathsFor(state.path)), changed: false }