@zzclub/pipeline 0.9.1 → 0.10.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.
- package/README.md +71 -8
- package/package.json +3 -4
- package/src/adapter-loader.ts +6 -1
- package/src/commands/analytics.test.ts +3 -2
- package/src/commands/analytics.ts +21 -4
- package/src/commands/attach-body-images.ts +23 -3
- package/src/commands/attach-body.ts +12 -3
- package/src/commands/attach-newspic-spec.ts +14 -3
- package/src/commands/config.ts +1 -0
- package/src/commands/ingest-handoff.ts +12 -6
- package/src/commands/init.ts +20 -36
- package/src/commands/prepare-finalize.ts +75 -43
- package/src/commands/prepare.ts +52 -22
- package/src/commands/publish.ts +63 -29
- package/src/commands/reconcile.ts +5 -3
- package/src/commands/render.ts +50 -9
- package/src/commands/republish.ts +73 -30
- package/src/commands/reset.test.ts +43 -0
- package/src/commands/reset.ts +25 -32
- package/src/commands/review.ts +5 -3
- package/src/commands/sync-blog.ts +20 -3
- package/src/commands/wechat-export.test.ts +41 -0
- package/src/commands/wechat-export.ts +22 -8
- package/src/config.ts +0 -0
- package/src/providers/blog.test.ts +37 -1
- package/src/providers/blog.ts +51 -13
- package/src/providers/cos.ts +23 -19
- package/src/providers/index.ts +34 -24
- package/src/providers/publish-core.ts +47 -26
- package/src/providers/wechat.test.ts +55 -0
- package/src/providers/wechat.ts +126 -14
- package/src/publish-targets.ts +142 -0
- package/src/routes.ts +9 -1
- package/src/schema/state.ts +1 -0
- package/src/state.test.ts +151 -0
- package/src/state.ts +267 -11
- package/src/task-manager.ts +62 -29
- package/src/wechat-preview/assets/browser-dist/.vite/manifest.json +1 -302
- package/src/wechat-preview/assets/browser-dist/editor-export.js +444 -958
- package/src/wechat-preview/browser/assets.d.ts +9 -0
- package/src/wechat-preview/browser/editor-export.css +276 -236
- package/src/wechat-preview/browser/editor-export.ts +24 -63
- package/src/wechat-preview/fixtures/all-nodes.md +68 -0
- package/src/wechat-preview/index.test.ts +17 -0
- package/src/wechat-preview/index.ts +44 -107
- package/src/wechat-preview/themes.ts +50 -45
- package/src/wechat-preview/wechat-export.integration.test.ts +94 -0
- package/src/wechat-preview/wechat-renderer.test.ts +48 -0
- package/src/wechat-preview/wechat-renderer.ts +652 -0
- package/src/wechat-preview.test.ts +18 -41
- package/src/workflow-materials.test.ts +46 -4
- package/src/workflow-materials.ts +41 -7
- package/src/workflow.test.ts +338 -27
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/src/wechat-preview/assets/browser-dist/assets/style-vhHYSP8T.css +0 -1
- package/src/wechat-preview/assets/fonts/SweiCurveLegCJKsc-Regular.woff +0 -0
- package/src/wechat-preview/wechat-formatter.test.ts +0 -287
- package/src/wechat-preview/wechat-formatter.ts +0 -1531
package/README.md
CHANGED
|
@@ -38,12 +38,13 @@ bun install --global .
|
|
|
38
38
|
|
|
39
39
|
这是一个状态机模型,输入是任务意图、正文、图片、路由和发布要求,输出是可恢复的工作流状态、渲染产物和发布结果。
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
当前有三条主工作流路由:
|
|
42
42
|
|
|
43
43
|
- `wechat-article` — 公众号文章草稿(HTML)
|
|
44
44
|
- `wechat-newspic` — 公众号图片消息(PNG 卡片/图集)
|
|
45
|
+
- `blog` — 将 Markdown 同步到配置的博客项目并执行发布命令
|
|
45
46
|
|
|
46
|
-
`blog`
|
|
47
|
+
`blog` 参与统一的 `prepare → publish` 工作流;作为唯一目标时跳过 render。`sync-blog` 继续保留为事后同步命令,两种路径都会把结果写回 `workflow-state.json` 的 `publish.results`。
|
|
47
48
|
|
|
48
49
|
状态文件分两类:
|
|
49
50
|
|
|
@@ -374,7 +375,7 @@ src/
|
|
|
374
375
|
wechat-preview/ # 微信文章 HTML 预览,Milkdown + 主题
|
|
375
376
|
index.ts
|
|
376
377
|
themes.ts
|
|
377
|
-
wechat-
|
|
378
|
+
wechat-renderer.ts # 元素 renderer 注册表、CSS 内联和微信安全清理
|
|
378
379
|
frontmatter-handler.ts
|
|
379
380
|
browser/
|
|
380
381
|
editor-export.ts
|
|
@@ -409,11 +410,12 @@ src/
|
|
|
409
410
|
| `review` | Update content review status |
|
|
410
411
|
| `abandon` | Mark one or more tasks as abandoned |
|
|
411
412
|
|
|
412
|
-
`ops` 组,
|
|
413
|
+
`ops` 组,12 个命令:
|
|
413
414
|
|
|
414
415
|
| 命令 | 说明 |
|
|
415
416
|
| --- | --- |
|
|
416
417
|
| `sync-blog` | Copy post.md to blog repo, publish, and record result in state JSON |
|
|
418
|
+
| `republish` | Publish a completed task to an additional account or platform |
|
|
417
419
|
| `imgx` | Run bundled imgx renderer subcommands |
|
|
418
420
|
| `wechat-export` | Render markdown to WeChat HTML with bundled preview styles |
|
|
419
421
|
| `cos-upload` | Upload a local image to configured COS CDN |
|
|
@@ -618,9 +620,57 @@ bun run src/cli.ts hermes-metrics --workspace /abs/workspace
|
|
|
618
620
|
### 微信 HTML 预览导出
|
|
619
621
|
|
|
620
622
|
```bash
|
|
621
|
-
bun run src/cli.ts wechat-export
|
|
623
|
+
bun run src/cli.ts wechat-export \
|
|
624
|
+
--markdown /abs/path/body.md \
|
|
625
|
+
--out /abs/path/article.html \
|
|
626
|
+
--account default \
|
|
627
|
+
--preview-shell-out /abs/path/article-preview.html
|
|
622
628
|
```
|
|
623
629
|
|
|
630
|
+
`--preview-shell-out` 生成最终发布 HTML 的精确预览:预览页直接嵌入 `--out`
|
|
631
|
+
中的内联样式片段,不再加载 Milkdown 编辑器或复制浏览器 bundle。
|
|
632
|
+
|
|
633
|
+
#### 自定义 CSS
|
|
634
|
+
|
|
635
|
+
可以通过账号配置或 CLI 覆盖内置样式:
|
|
636
|
+
|
|
637
|
+
```json
|
|
638
|
+
{
|
|
639
|
+
"wx": {
|
|
640
|
+
"accounts": {
|
|
641
|
+
"default": {
|
|
642
|
+
"customCss": "./themes/default-wechat.css"
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
```bash
|
|
650
|
+
bun run src/cli.ts wechat-export \
|
|
651
|
+
--markdown /abs/path/body.md \
|
|
652
|
+
--out /abs/path/article.html \
|
|
653
|
+
--custom-css ./local-wechat.css
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
样式优先级固定为:内置排版 CSS → 账号主题令牌 → 账号 `customCss`。传入
|
|
657
|
+
`--custom-css` 时,它会替代账号 `customCss`。CLI 相对路径按当前工作目录解析,
|
|
658
|
+
配置中的相对路径按 `config.json` 所在目录解析。
|
|
659
|
+
|
|
660
|
+
推荐使用语义标签或稳定节点钩子:
|
|
661
|
+
|
|
662
|
+
```css
|
|
663
|
+
.milkdown .editor p { line-height: 1.9; }
|
|
664
|
+
.milkdown .editor h2 { color: #a94473; }
|
|
665
|
+
.milkdown .editor [data-wechat-node="inline-code"] { background: #f7eef2; }
|
|
666
|
+
.milkdown .editor [data-wechat-node="image-caption"] { font-size: 13px; }
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
`.milkdown .editor` 只是 CSS 内联阶段的兼容包装;最终 HTML 会移除 `class` 和
|
|
670
|
+
`data-wechat-node`。外部 CSS 支持变量、层叠和常规选择器,随后由 Juice 解析为
|
|
671
|
+
内联样式。最终片段只保留微信稳定标签、图片/链接/表格必要属性和保守 CSS;
|
|
672
|
+
脚本、事件属性、外部 `<style>`、未解析变量、`flex`/`grid`、定位和伪元素都会被移除。
|
|
673
|
+
|
|
624
674
|
### COS 图片上传
|
|
625
675
|
|
|
626
676
|
```bash
|
|
@@ -796,10 +846,23 @@ bun run src/cli.ts init ... --note-id NOTE_ID
|
|
|
796
846
|
|
|
797
847
|
## wechat-preview 子系统
|
|
798
848
|
|
|
799
|
-
- 基于 Milkdown
|
|
800
|
-
-
|
|
849
|
+
- 基于 Milkdown core + CommonMark/GFM 生成语义 HTML,不读取 Crepe/CodeMirror 可视 DOM
|
|
850
|
+
- 固定管线:Markdown → Milkdown HTML → 元素 renderer prepare → Juice 内联 → renderer finalize → 微信白名单清理
|
|
851
|
+
- 元素 renderer 覆盖段落/标题、强调、行内代码、代码块、引用、图片标题、链接脚注、列表、表格、分隔线和页脚
|
|
852
|
+
- 内置默认采用“静默编辑”:深色标题、低装饰引用和中性行内代码,品牌色只用于链接等识别点
|
|
853
|
+
- `default` 与 `ancientone` 共用排版骨架,只通过主题令牌切换品牌色、字体栈和页脚
|
|
801
854
|
- 构建命令:`bun run build:wechat-preview`
|
|
802
|
-
- `wechat-export`
|
|
855
|
+
- `wechat-export` 和微信草稿发布使用同一份最终内联 HTML
|
|
856
|
+
|
|
857
|
+
`src/wechat-preview/browser/editor-export.css` 会通过 Vite 以 raw CSS 打入浏览器
|
|
858
|
+
bundle。修改该 CSS 或浏览器 renderer 后必须执行:
|
|
859
|
+
|
|
860
|
+
```bash
|
|
861
|
+
bun run build:wechat-preview
|
|
862
|
+
bun install --global .
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
全局 `zzp` 使用编译产物;只修改源码而不重新全局安装不会更新全局命令。
|
|
803
866
|
|
|
804
867
|
## 插件系统
|
|
805
868
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zzclub/pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"zzp": "./src/cli.ts",
|
|
@@ -34,12 +34,11 @@
|
|
|
34
34
|
"cos-nodejs-sdk-v5": "^2.15.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@milkdown/
|
|
38
|
-
"@milkdown/plugin-listener": "^7.18.0",
|
|
39
|
-
"@milkdown/utils": "^7.18.0",
|
|
37
|
+
"@milkdown/kit": "^7.18.0",
|
|
40
38
|
"@types/bun": "latest",
|
|
41
39
|
"@types/pngjs": "^6.0.5",
|
|
42
40
|
"changelogen": "^0.6.2",
|
|
41
|
+
"juice": "11.1.1",
|
|
43
42
|
"typescript": "^5.7.0",
|
|
44
43
|
"vite": "^7.1.10"
|
|
45
44
|
}
|
package/src/adapter-loader.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { PipelineConfig } from "./schema/config";
|
|
10
|
+
import { isAbsolute, resolve } from "path";
|
|
11
|
+
import { pathToFileURL } from "url";
|
|
10
12
|
import type {
|
|
11
13
|
ImageRenderPlugin,
|
|
12
14
|
MarkdownRenderPlugin,
|
|
@@ -49,7 +51,10 @@ function validateMarkdownRenderPlugin(plugin: unknown, specifier: string): Markd
|
|
|
49
51
|
|
|
50
52
|
async function importPlugin(specifier: string): Promise<unknown> {
|
|
51
53
|
try {
|
|
52
|
-
const
|
|
54
|
+
const importSpecifier = specifier.startsWith(".") || isAbsolute(specifier)
|
|
55
|
+
? pathToFileURL(resolve(specifier)).href
|
|
56
|
+
: specifier;
|
|
57
|
+
const mod = await import(importSpecifier);
|
|
53
58
|
// Support both default exports and named `plugin` exports
|
|
54
59
|
return mod.default ?? mod.plugin ?? mod;
|
|
55
60
|
} catch (err) {
|
|
@@ -123,11 +123,12 @@ describe("analytics commands", () => {
|
|
|
123
123
|
|
|
124
124
|
test("filters by days", async () => {
|
|
125
125
|
const statePath = join(workspace, "state.json");
|
|
126
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
126
127
|
await writeFile(statePath, JSON.stringify({
|
|
127
128
|
run_id: "run_001",
|
|
128
|
-
metadata: { title: "Article", date:
|
|
129
|
+
metadata: { title: "Article", date: today },
|
|
129
130
|
route: { primary: "wechat-article" },
|
|
130
|
-
publish: { results: { wechat: { status: "success", published_at:
|
|
131
|
+
publish: { results: { wechat: { status: "success", published_at: `${today}T10:00:00Z` } } },
|
|
131
132
|
}));
|
|
132
133
|
|
|
133
134
|
await recordAnalytics({ statePath, reads: 1000 });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
|
-
import { dirname } from "path";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
3
3
|
import { ensureDb } from "../db";
|
|
4
4
|
import { AnalyticsSchema, type Analytics } from "../schema/analytics";
|
|
5
5
|
import { parseArgs, requireArg, optionalArg } from "../args";
|
|
@@ -26,10 +26,27 @@ function resolveWorkspace(statePath: string): string {
|
|
|
26
26
|
export async function recordAnalytics(
|
|
27
27
|
input: RecordAnalyticsInput,
|
|
28
28
|
): Promise<Analytics> {
|
|
29
|
-
const
|
|
30
|
-
|
|
29
|
+
const requestedPath = resolve(input.statePath);
|
|
30
|
+
let statePath = requestedPath;
|
|
31
|
+
let state = JSON.parse(await readFile(statePath, "utf-8"));
|
|
32
|
+
if (typeof state.state_path === "string" && state.state_path.trim()) {
|
|
33
|
+
const candidatePath = isAbsolute(state.state_path)
|
|
34
|
+
? resolve(state.state_path)
|
|
35
|
+
: resolve(dirname(statePath), state.state_path);
|
|
36
|
+
if (candidatePath !== statePath) {
|
|
37
|
+
try {
|
|
38
|
+
state = JSON.parse(await readFile(candidatePath, "utf-8"));
|
|
39
|
+
statePath = candidatePath;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const code = error instanceof Error && "code" in error
|
|
42
|
+
? String((error as NodeJS.ErrnoException).code)
|
|
43
|
+
: "";
|
|
44
|
+
if (code !== "ENOENT") throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
31
48
|
|
|
32
|
-
const workspace = resolveWorkspace(
|
|
49
|
+
const workspace = state.workspace_root || resolveWorkspace(statePath);
|
|
33
50
|
const db = ensureDb(workspace);
|
|
34
51
|
|
|
35
52
|
try {
|
|
@@ -2,7 +2,13 @@ import { readFile } from "fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs, optionalArg, requireArg } from "../args";
|
|
4
4
|
import { printResult, renderTaskShape } from "../output";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
readResolvedState,
|
|
7
|
+
reenterPublish,
|
|
8
|
+
reenterRender,
|
|
9
|
+
writeState,
|
|
10
|
+
type BodyInputReceived,
|
|
11
|
+
} from "../state";
|
|
6
12
|
import { getTaskByStatePath } from "../task-manager";
|
|
7
13
|
import { reconcileStateArtifacts } from "../workflow-materials";
|
|
8
14
|
|
|
@@ -52,12 +58,15 @@ Options:
|
|
|
52
58
|
return;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
const
|
|
61
|
+
const requestedStatePath = requireArg(parsed, "state", "state JSON path");
|
|
56
62
|
const imagesFile = requireArg(parsed, "images-file", "JSON file with marker/path data");
|
|
57
63
|
const scope = optionalArg(parsed, "scope");
|
|
58
64
|
const layout = optionalArg(parsed, "layout");
|
|
59
65
|
|
|
60
|
-
const
|
|
66
|
+
const resolved = await readResolvedState(requestedStatePath);
|
|
67
|
+
const statePath = resolved.path;
|
|
68
|
+
const state = resolved.state;
|
|
69
|
+
const prepareWasDone = state.phase.prepare.status === "done";
|
|
61
70
|
const raw = JSON.parse(await readFile(imagesFile, "utf-8")) as unknown;
|
|
62
71
|
const incoming = parseReceivedImages(raw);
|
|
63
72
|
const merged = new Map(state.images.body_inputs.received.map((item) => [item.marker, item.path]));
|
|
@@ -74,6 +83,17 @@ Options:
|
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
await reconcileStateArtifacts(state);
|
|
86
|
+
if (prepareWasDone && state.images.body_inputs.scope === "newspic-longform") {
|
|
87
|
+
reenterRender(state);
|
|
88
|
+
} else if (
|
|
89
|
+
prepareWasDone &&
|
|
90
|
+
state.images.body_inputs.scope === "article" &&
|
|
91
|
+
(state.phase.current === "publish" ||
|
|
92
|
+
state.phase.current === "done" ||
|
|
93
|
+
state.phase.publish.status === "done")
|
|
94
|
+
) {
|
|
95
|
+
reenterPublish(state);
|
|
96
|
+
}
|
|
77
97
|
await writeState(statePath, state);
|
|
78
98
|
|
|
79
99
|
const task = await getTaskByStatePath(statePath);
|
|
@@ -4,7 +4,7 @@ import { extname, join, resolve } from "path";
|
|
|
4
4
|
import { parseArgs, optionalArg, requireArg } from "../args";
|
|
5
5
|
import { printResult, renderTaskShape } from "../output";
|
|
6
6
|
import { resolveWorkspacePaths } from "../config";
|
|
7
|
-
import {
|
|
7
|
+
import { readResolvedState, reenterPrepare, writeState } from "../state";
|
|
8
8
|
import { getTaskByStatePath } from "../task-manager";
|
|
9
9
|
import { reconcileStateArtifacts } from "../workflow-materials";
|
|
10
10
|
|
|
@@ -48,7 +48,7 @@ Options:
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const requestedStatePath = requireArg(parsed, "state", "state JSON path");
|
|
52
52
|
const bodyPath = optionalArg(parsed, "body");
|
|
53
53
|
const bodyText = optionalArg(parsed, "body-text");
|
|
54
54
|
if (!bodyPath && bodyText === undefined) {
|
|
@@ -58,13 +58,22 @@ Options:
|
|
|
58
58
|
throw new Error("Use either --body or --body-text, not both");
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const
|
|
61
|
+
const resolved = await readResolvedState(requestedStatePath);
|
|
62
|
+
const statePath = resolved.path;
|
|
63
|
+
const state = resolved.state;
|
|
62
64
|
state.source_body_path = await stageManagedBodyFile(
|
|
63
65
|
state.workspace_root,
|
|
64
66
|
state.run_id,
|
|
65
67
|
bodyPath ?? null,
|
|
66
68
|
bodyText ?? null,
|
|
67
69
|
);
|
|
70
|
+
reenterPrepare(state, {
|
|
71
|
+
clearFormattedBody: true,
|
|
72
|
+
resetReview: true,
|
|
73
|
+
});
|
|
74
|
+
if (state.handoff.review_policy === "trust_user") {
|
|
75
|
+
state.content_review = { status: "passed", feedback: null };
|
|
76
|
+
}
|
|
68
77
|
await reconcileStateArtifacts(state);
|
|
69
78
|
await writeState(statePath, state);
|
|
70
79
|
|
|
@@ -2,7 +2,12 @@ import { readFile } from "fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import { parseArgs, requireArg } from "../args";
|
|
4
4
|
import { printResult, renderTaskShape } from "../output";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
normalizeNewspicRenderSpec,
|
|
7
|
+
readResolvedState,
|
|
8
|
+
reenterRender,
|
|
9
|
+
writeState,
|
|
10
|
+
} from "../state";
|
|
6
11
|
import { getTaskByStatePath } from "../task-manager";
|
|
7
12
|
import { reconcileStateArtifacts } from "../workflow-materials";
|
|
8
13
|
|
|
@@ -20,13 +25,19 @@ Options:
|
|
|
20
25
|
return;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
const
|
|
28
|
+
const requestedStatePath = requireArg(parsed, "state", "state JSON path");
|
|
24
29
|
const specPath = requireArg(parsed, "file", "newspic render spec file");
|
|
25
30
|
|
|
26
|
-
const
|
|
31
|
+
const resolved = await readResolvedState(requestedStatePath);
|
|
32
|
+
const statePath = resolved.path;
|
|
33
|
+
const state = resolved.state;
|
|
34
|
+
const prepareWasDone = state.phase.prepare.status === "done";
|
|
27
35
|
const spec = JSON.parse(await readFile(specPath, "utf-8")) as unknown;
|
|
28
36
|
state.intent.newspic_render = normalizeNewspicRenderSpec(spec);
|
|
29
37
|
await reconcileStateArtifacts(state);
|
|
38
|
+
if (prepareWasDone) {
|
|
39
|
+
reenterRender(state);
|
|
40
|
+
}
|
|
30
41
|
await writeState(statePath, state);
|
|
31
42
|
|
|
32
43
|
const task = await getTaskByStatePath(statePath);
|
package/src/commands/config.ts
CHANGED
|
@@ -67,6 +67,7 @@ Options:
|
|
|
67
67
|
wx: mergedWx,
|
|
68
68
|
cos: { ...config.cos, ...(importedObj.cos ?? {}) },
|
|
69
69
|
plugins: { ...config.plugins, ...(importedObj.plugins ?? {}) },
|
|
70
|
+
imgx: { ...config.imgx, ...(importedObj.imgx ?? {}) },
|
|
70
71
|
};
|
|
71
72
|
// Validate through Zod schema — strips unknown fields, applies defaults
|
|
72
73
|
const merged = PipelineConfigSchema.parse(raw);
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
defaultState,
|
|
16
16
|
generateRunId,
|
|
17
17
|
getRunStatePath,
|
|
18
|
-
|
|
18
|
+
readResolvedState,
|
|
19
19
|
writeState,
|
|
20
20
|
type ContentForm,
|
|
21
21
|
type HandoffAuthoringPolicy,
|
|
@@ -473,10 +473,16 @@ Options:
|
|
|
473
473
|
handoff.mode === "resume"
|
|
474
474
|
? await resolveExistingStatePath(workspace, handoff)
|
|
475
475
|
: getRunStatePath(workspace, generateRunId());
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
476
|
+
const resolvedExisting = handoff.mode === "resume"
|
|
477
|
+
? await readResolvedState(statePath)
|
|
478
|
+
: null;
|
|
479
|
+
const effectiveStatePath = resolvedExisting?.path ?? statePath;
|
|
480
|
+
const state = resolvedExisting?.state ??
|
|
481
|
+
buildNewState(
|
|
482
|
+
workspace,
|
|
483
|
+
effectiveStatePath,
|
|
484
|
+
effectiveStatePath.split("/").pop()?.replace(/\.json$/, "") ?? generateRunId(),
|
|
485
|
+
);
|
|
480
486
|
|
|
481
487
|
if (handoff.mode === "new" && !state.run_id) {
|
|
482
488
|
state.run_id = generateRunId();
|
|
@@ -484,7 +490,7 @@ Options:
|
|
|
484
490
|
}
|
|
485
491
|
|
|
486
492
|
await applyHandoffToState(state, handoff);
|
|
487
|
-
await writeState(state.state_path, state);
|
|
493
|
+
await writeState(state.state_path || effectiveStatePath, state);
|
|
488
494
|
|
|
489
495
|
const task = await getTaskByStatePath(state.state_path);
|
|
490
496
|
printResult({
|
package/src/commands/init.ts
CHANGED
|
@@ -28,17 +28,19 @@ import { parseArgs, requireArg, optionalArg, flagArg } from "../args";
|
|
|
28
28
|
import { printResult, renderInit } from "../output";
|
|
29
29
|
import { loadConfig, resolveWorkspaceRoot } from "../config";
|
|
30
30
|
import { resolveFullRoute } from "../routes";
|
|
31
|
+
import {
|
|
32
|
+
parseAccountName,
|
|
33
|
+
parseContentForm,
|
|
34
|
+
parseContentOrigin,
|
|
35
|
+
parsePublishTargets,
|
|
36
|
+
parseTaskKind,
|
|
37
|
+
} from "../publish-targets";
|
|
31
38
|
import {
|
|
32
39
|
defaultState,
|
|
33
40
|
generateRunId,
|
|
34
41
|
getRunStatePath,
|
|
35
42
|
normalizeNewspicRenderSpec,
|
|
36
43
|
writeState,
|
|
37
|
-
type ContentForm,
|
|
38
|
-
type ContentOrigin,
|
|
39
|
-
type PublishTarget,
|
|
40
|
-
type Target,
|
|
41
|
-
type TaskKind,
|
|
42
44
|
} from "../state";
|
|
43
45
|
|
|
44
46
|
export async function init(args: string[]): Promise<void> {
|
|
@@ -67,10 +69,10 @@ Options:
|
|
|
67
69
|
return;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
const taskKind = requireArg(parsed, "task-kind", "task kind")
|
|
71
|
-
const contentForm = requireArg(parsed, "content-form", "content form")
|
|
72
|
+
const taskKind = parseTaskKind(requireArg(parsed, "task-kind", "task kind"));
|
|
73
|
+
const contentForm = parseContentForm(requireArg(parsed, "content-form", "content form"));
|
|
72
74
|
const targetsRaw = requireArg(parsed, "targets", "publish targets");
|
|
73
|
-
const contentOrigin = requireArg(parsed, "content-origin", "content origin")
|
|
75
|
+
const contentOrigin = parseContentOrigin(requireArg(parsed, "content-origin", "content origin"));
|
|
74
76
|
const intentText = optionalArg(parsed, "intent-text") ?? "";
|
|
75
77
|
const accountOverride = optionalArg(parsed, "account");
|
|
76
78
|
const styleHint = optionalArg(parsed, "style-hint") ?? null;
|
|
@@ -80,31 +82,13 @@ Options:
|
|
|
80
82
|
const config = loadConfig();
|
|
81
83
|
const workspace = resolveWorkspaceRoot(optionalArg(parsed, "workspace"), config);
|
|
82
84
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const publishTargets
|
|
89
|
-
const
|
|
90
|
-
const defaultAccount = accountOverride || "default";
|
|
91
|
-
const mappedTargets: Target[] = [];
|
|
92
|
-
|
|
93
|
-
for (const part of targetParts) {
|
|
94
|
-
const atIdx = part.indexOf("@");
|
|
95
|
-
const route = atIdx !== -1 ? part.slice(0, atIdx).trim() : part;
|
|
96
|
-
const account = atIdx !== -1 ? part.slice(atIdx + 1).trim() : defaultAccount;
|
|
97
|
-
publishTargets.push({ route: route as any, account });
|
|
98
|
-
// Map RoutePrimary to Target enum: wechat-* → "wechat", blog → "blog"
|
|
99
|
-
mappedTargets.push(route.startsWith("wechat") ? "wechat" : route as Target);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Single target: leave publish_targets empty (backward compat)
|
|
103
|
-
// Multi-target: populate publish_targets
|
|
104
|
-
const isMultiTarget = publishTargets.length > 1 || targetsRaw.includes("@");
|
|
105
|
-
|
|
106
|
-
// Use mapped targets (Target enum) for intent, not raw RoutePrimary values
|
|
107
|
-
const intentTargets = mappedTargets.length > 0 ? mappedTargets : targets;
|
|
85
|
+
const defaultAccount = parseAccountName(accountOverride ?? "default");
|
|
86
|
+
const parsedTargets = parsePublishTargets(targetsRaw, {
|
|
87
|
+
contentForm,
|
|
88
|
+
defaultAccount,
|
|
89
|
+
});
|
|
90
|
+
const publishTargets = parsedTargets.targets;
|
|
91
|
+
const intentTargets = parsedTargets.intentTargets;
|
|
108
92
|
|
|
109
93
|
const runId = generateRunId();
|
|
110
94
|
const statePath = getRunStatePath(workspace, runId);
|
|
@@ -121,16 +105,16 @@ Options:
|
|
|
121
105
|
state.state_path = statePath;
|
|
122
106
|
state.mode = "active";
|
|
123
107
|
state.route = resolveFullRoute(intentText, {
|
|
124
|
-
account: accountOverride,
|
|
108
|
+
account: accountOverride ? defaultAccount : undefined,
|
|
125
109
|
contentForm,
|
|
126
110
|
targets: intentTargets,
|
|
127
111
|
});
|
|
128
112
|
|
|
129
113
|
// Set publish_targets if multi-target
|
|
130
|
-
if (
|
|
114
|
+
if (parsedTargets.explicit && publishTargets.length > 0) {
|
|
131
115
|
state.publish_targets = publishTargets;
|
|
132
116
|
// First target becomes primary route
|
|
133
|
-
state.route.primary = publishTargets[0].route
|
|
117
|
+
state.route.primary = publishTargets[0].route;
|
|
134
118
|
state.route.account = publishTargets[0].account;
|
|
135
119
|
}
|
|
136
120
|
|