nx-sk 0.23.1 → 0.28.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.
Files changed (165) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/README.md +18 -5
  3. package/assets/nx-sk/SKILL.md +6 -1
  4. package/assets/nx-sk/references/10-sections-entries.md +46 -4
  5. package/assets/nx-sk/references/15-special-fields.md +27 -14
  6. package/assets/sk-source/SKILL.md +2 -2
  7. package/assets/sk-source/references/20-intake.md +1 -1
  8. package/bin/nx-sk.mjs +21 -3
  9. package/{src → dist}/core/argsafe.js +24 -24
  10. package/dist/core/attachments.js +463 -0
  11. package/dist/core/crypto.js +71 -0
  12. package/{src → dist}/core/errors.js +27 -29
  13. package/dist/core/fields/behaviors/bool.js +24 -0
  14. package/dist/core/fields/behaviors/common.js +37 -0
  15. package/dist/core/fields/behaviors/date.js +12 -0
  16. package/dist/core/fields/behaviors/entrylist.js +112 -0
  17. package/dist/core/fields/behaviors/file.js +38 -0
  18. package/dist/core/fields/behaviors/md.js +21 -0
  19. package/dist/core/fields/behaviors/month.js +12 -0
  20. package/dist/core/fields/behaviors/number.js +18 -0
  21. package/dist/core/fields/behaviors/secret.js +12 -0
  22. package/dist/core/fields/behaviors/select.js +12 -0
  23. package/dist/core/fields/behaviors/source.js +71 -0
  24. package/dist/core/fields/behaviors/table.js +185 -0
  25. package/dist/core/fields/behaviors/tags.js +20 -0
  26. package/dist/core/fields/behaviors/text.js +12 -0
  27. package/dist/core/fields/behaviors/textarea.js +12 -0
  28. package/dist/core/fields/registry.js +85 -0
  29. package/dist/core/fields/seal/common.js +39 -0
  30. package/dist/core/fields/seal/entrylist.js +46 -0
  31. package/dist/core/fields/seal/index.js +23 -0
  32. package/dist/core/fields/seal/table.js +86 -0
  33. package/dist/core/fields.js +828 -0
  34. package/dist/core/fsx.js +119 -0
  35. package/dist/core/ids.js +16 -0
  36. package/{src → dist}/core/mdimages.js +35 -35
  37. package/dist/core/open.js +36 -0
  38. package/{src → dist}/core/paths.js +57 -71
  39. package/dist/core/render.js +167 -0
  40. package/dist/core/securezip.js +68 -0
  41. package/dist/core/sensitive.js +27 -0
  42. package/{src → dist}/core/serve-port.js +59 -61
  43. package/dist/core/source.js +261 -0
  44. package/dist/core/store.js +296 -0
  45. package/dist/core/values.js +42 -0
  46. package/dist/core/vault.js +131 -0
  47. package/dist/core/zip.js +198 -0
  48. package/dist/index.js +12 -0
  49. package/dist/modules/entries/index.js +280 -0
  50. package/dist/modules/entries/service.js +1239 -0
  51. package/dist/modules/files/index.js +123 -0
  52. package/dist/modules/files/service.js +328 -0
  53. package/dist/modules/sections/index.js +142 -0
  54. package/dist/modules/sections/service.js +806 -0
  55. package/dist/modules/settings/index.js +32 -0
  56. package/dist/modules/settings/service.js +234 -0
  57. package/dist/modules/skill/index.js +50 -0
  58. package/dist/modules/skill/service.js +238 -0
  59. package/dist/modules/system/index.js +40 -0
  60. package/dist/modules/system/service.js +157 -0
  61. package/dist/modules/transfer/index.js +112 -0
  62. package/dist/modules/transfer/service.js +1004 -0
  63. package/dist/runtime/api.js +139 -0
  64. package/dist/runtime/cli.js +388 -0
  65. package/dist/runtime/module-registry.js +88 -0
  66. package/dist/runtime/registry.js +62 -0
  67. package/dist/runtime/server.js +78 -0
  68. package/dist/runtime/spec.js +193 -0
  69. package/dist/shared/blockLayout.js +382 -0
  70. package/dist/shared/fields/types.js +36 -0
  71. package/dist/shared/model.js +1 -0
  72. package/dist/shared/table.js +957 -0
  73. package/{src/web/public/assets/files-Cr-oMOeX.js → dist/web/public/assets/files-DDDN-GbJ.js} +1 -1
  74. package/dist/web/public/assets/index-BpUTnBjj.js +41 -0
  75. package/dist/web/public/assets/index-DDEmDb0K.css +1 -0
  76. package/dist/web/public/assets/view-BgznDLss.js +46 -0
  77. package/{src/web/public/assets/view-DYFqX9mq.js → dist/web/public/assets/view-C6I3Vf9F.js} +2 -2
  78. package/dist/web/public/assets/view-CgLZr6WS.css +1 -0
  79. package/{src/web/public/assets/view-7t9TLUNM.js → dist/web/public/assets/view-ClLIweOz.js} +1 -1
  80. package/dist/web/public/assets/view-DOpzNX1y.js +3 -0
  81. package/dist/web/public/assets/view-TJPSoVht.js +1 -0
  82. package/{src/web/frontend → dist/web/public}/index.html +2 -1
  83. package/package.json +16 -8
  84. package/src/core/attachments.js +0 -446
  85. package/src/core/crypto.js +0 -73
  86. package/src/core/fields.js +0 -895
  87. package/src/core/fsx.js +0 -103
  88. package/src/core/ids.js +0 -19
  89. package/src/core/open.js +0 -32
  90. package/src/core/render.js +0 -233
  91. package/src/core/securezip.js +0 -72
  92. package/src/core/source.js +0 -230
  93. package/src/core/store.js +0 -293
  94. package/src/core/vault.js +0 -125
  95. package/src/core/zip.js +0 -198
  96. package/src/index.js +0 -13
  97. package/src/modules/entries/index.js +0 -286
  98. package/src/modules/entries/kvTable.jsx +0 -165
  99. package/src/modules/entries/service.js +0 -1508
  100. package/src/modules/entries/view.jsx +0 -628
  101. package/src/modules/export/index.js +0 -14
  102. package/src/modules/export/view.jsx +0 -25
  103. package/src/modules/files/index.js +0 -125
  104. package/src/modules/files/service.js +0 -327
  105. package/src/modules/files/view.jsx +0 -400
  106. package/src/modules/sections/index.js +0 -142
  107. package/src/modules/sections/service.js +0 -786
  108. package/src/modules/sections/view.jsx +0 -1199
  109. package/src/modules/settings/index.js +0 -34
  110. package/src/modules/settings/navManager.jsx +0 -213
  111. package/src/modules/settings/service.js +0 -221
  112. package/src/modules/settings/view.jsx +0 -224
  113. package/src/modules/skill/index.js +0 -52
  114. package/src/modules/skill/service.js +0 -237
  115. package/src/modules/system/index.js +0 -42
  116. package/src/modules/system/service.js +0 -163
  117. package/src/modules/transfer/index.js +0 -113
  118. package/src/modules/transfer/service.js +0 -958
  119. package/src/runtime/api.js +0 -140
  120. package/src/runtime/cli.js +0 -352
  121. package/src/runtime/registry.js +0 -67
  122. package/src/runtime/server.js +0 -80
  123. package/src/runtime/spec.js +0 -169
  124. package/src/shared/table.js +0 -884
  125. package/src/web/frontend/App.jsx +0 -274
  126. package/src/web/frontend/activeView.js +0 -42
  127. package/src/web/frontend/api/client.js +0 -26
  128. package/src/web/frontend/api/files.js +0 -49
  129. package/src/web/frontend/api/zip.js +0 -34
  130. package/src/web/frontend/components/fieldEditor.jsx +0 -737
  131. package/src/web/frontend/components/lightbox.jsx +0 -96
  132. package/src/web/frontend/components/listDrag.jsx +0 -82
  133. package/src/web/frontend/components/mdView.jsx +0 -193
  134. package/src/web/frontend/components/navModel.js +0 -293
  135. package/src/web/frontend/components/sourcePanel.jsx +0 -558
  136. package/src/web/frontend/components/tableField.jsx +0 -477
  137. package/src/web/frontend/components/transferPanel.jsx +0 -494
  138. package/src/web/frontend/components/ui.jsx +0 -152
  139. package/src/web/frontend/listSort.js +0 -18
  140. package/src/web/frontend/main.jsx +0 -13
  141. package/src/web/frontend/mdInline.js +0 -185
  142. package/src/web/frontend/mdSmart.js +0 -164
  143. package/src/web/frontend/mdSource.js +0 -41
  144. package/src/web/frontend/registry.js +0 -21
  145. package/src/web/frontend/store.jsx +0 -107
  146. package/src/web/frontend/styles.css +0 -1229
  147. package/src/web/public/assets/index-BiDzPPaq.css +0 -1
  148. package/src/web/public/assets/index-DPjpWSaw.js +0 -41
  149. package/src/web/public/assets/view-C8iiWh9-.js +0 -3
  150. package/src/web/public/assets/view-CpkUF3Xr.js +0 -1
  151. package/src/web/public/assets/view-qaSrOBcK.js +0 -42
  152. package/src/web/public/favicon-16.png +0 -0
  153. package/src/web/public/favicon-32.png +0 -0
  154. package/src/web/public/favicon-48.png +0 -0
  155. package/src/web/public/favicon.ico +0 -0
  156. package/src/web/public/index.html +0 -17
  157. package/src/web/public/logo-rounded.png +0 -0
  158. package/src/web/public/logo.png +0 -0
  159. /package/{src → dist}/web/public/assets/zip-gQ69BVQr.js +0 -0
  160. /package/{src/web/frontend → dist/web}/public/favicon-16.png +0 -0
  161. /package/{src/web/frontend → dist/web}/public/favicon-32.png +0 -0
  162. /package/{src/web/frontend → dist/web}/public/favicon-48.png +0 -0
  163. /package/{src/web/frontend → dist/web}/public/favicon.ico +0 -0
  164. /package/{src/web/frontend → dist/web}/public/logo-rounded.png +0 -0
  165. /package/{src/web/frontend → dist/web}/public/logo.png +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,153 @@
2
2
 
3
3
  本文件格式参考 [Keep a Changelog](https://keepachangelog.com/),中文正文。
4
4
 
5
+ ## 0.28.0 (2026-11-10)
6
+
7
+ **全量 TypeScript 化:后端与前端源码统一为 `.ts/.tsx`;字段与模块改为注册表驱动。对外行为不变。**
8
+
9
+ ### Changed
10
+
11
+ - **源码全量 TypeScript**:`src/**` 不再有 `.js/.jsx`;`pnpm build` 先用 `tsc -p tsconfig.build.json`
12
+ 产出 `dist/**/*.js`(`.ts` 导入后缀在 emit 时改写),再把面板构建到 `dist/web/public`。
13
+ - **字段收敛为穷举注册表**:服务端 `FIELD_BEHAVIOR` / `FIELD_SEAL`、前端 `FIELD_CONTROLS`
14
+ 都是 `Record<FieldType, …>`;`FIELD_TYPES` 加一项而漏实现/漏控件,会直接编译失败。
15
+ - **模块「目录即声明」**:`src/modules/<id>/index.ts` 自动装载,新增模块不再需要改注册表与 eslint 清单。
16
+ - **前端只消费 `shared`**:类型与零依赖纯函数从 `src/shared/**` 引入,eslint 边界规则覆盖 `.ts/.tsx`,
17
+ 继续拦住 `import '../../core/**'` 与 `import '../../modules/**'`。
18
+
19
+ ### Fixed
20
+
21
+ - **导出 ZIP 时表格的敏感列按列定义解密**:此前 `plainValues` 只处理 `entrylist`,含敏感列的表格会带密文出包,
22
+ 换机器后永久不可解(独立提交 `fc4b9e2`)。
23
+
24
+ ### 说明
25
+
26
+ - 命令、HTTP 响应形状、面板文案与交互、`store.json` 格式均未变化;`engines.node` 仍是 `>=18.17`。
27
+ - 贡献者:单测直接执行 `.ts` 需要 Node `>=22.18`;改了后端源码要重新 `pnpm build`。
28
+
29
+ ## 0.27.0 (2026-11-08)
30
+
31
+ **表格字段重做:列头 ⋮ 属性菜单、行列都有抓手、去掉了那一整块「列设置」表单。**
32
+
33
+ 原来的表格能用,但长得像后台配置页:表格下面铺着一整块「列设置」,一列一行、七八个输入框
34
+ (key / 名称 / 类型 / 汇总 / 单位 / 默认值 / 密文 / ←→×),而行与列的操作散在两端。
35
+ 这一版按 Notion 数据库的手感重排:**表头就是属性入口,操作都收在 hover 里,静止时这页只有数据**。
36
+
37
+ ### Changed
38
+
39
+ - **列头 ⋮ 打开属性菜单**:列名 / 类型(含「计算列(JS)」)/ key / 单位 / 默认值 / 表尾统计 / 密文 /
40
+ 下拉选项 / 公式,以及左移右移、左右插入、复制属性、删除属性。弹层按锚点定位,空间不够自动翻到上方。
41
+ - **表头末尾 `+`**:加列的入口长在列该长的位置;原来底栏那个「+ 添加列」只在空表时出现。
42
+ - **行抓手挪到左侧沟槽**:静止显示行号,hover 变成 `⋮`(在上/下方插入、复制、删除)+ `⠿`(拖动排序)。
43
+ 原来行操作挤在行尾 78px 的操作列里,现在表格右侧干净了。
44
+ - **表尾「新建一行」**:加行不用回工具栏;回车在最后一格仍然能续行。
45
+ - **质感**:格子不再是「描边输入框」——无边框、无底色,只有 1px 网格线;hover 整行浮起,
46
+ focus 才在格子里显一圈极淡边界。表头小字灰色、计算列浅底、统计行加粗、数字等宽右对齐。
47
+ - 删掉了整块「列设置」面板(`.tf-cols` / `.tf-colrow`),列定义现在只在列头弹层里改。
48
+
49
+ ### Fixed
50
+
51
+ - **勾选列被撑成一整格黑块**:复选框套了给文本框用的 `.tf-input`(width/height 100%),
52
+ 现在单独用 `.tf-check`(15px)。
53
+ - **弹层跑到屏幕左上角**:行/列的 `⋮` 原本用 `display:none` 藏在 hover 里,键盘或脚本点开时
54
+ `getBoundingClientRect()` 全是 0。改成「常驻布局 + opacity 切换」,行号绝对定位叠在同一格上。
55
+
56
+ ## 0.26.0 (2026-11-08)
57
+
58
+ **原始资料(`source`)改成真正的侧边栏;块头 / 面板标题整行都能拖。**
59
+
60
+ 它本来就有 master-detail 的骨架(左清单 / 右正文),但外面套的是「全屏遮罩 + 模态 dialog」:
61
+ 打开时整页被压暗、还被块的 `overflow: hidden` 裁掉一角——整理素材时看不见原文,
62
+ 而「整理」恰恰要求两边对着看。
63
+
64
+ ### Changed
65
+
66
+ - `SourceDrawer` 用 `createPortal` 挂到 `document.body`,定位改为**窗口右侧通栏**
67
+ (`position: fixed`,宽 `min(720px, 46vw)`,窄屏 94vw),带一条柔和投影与轻微的滑入动画。
68
+ - **去掉遮罩与 `aria-modal`**:改成 `role="complementary"`,左边条目仍然可读、可滚;
69
+ 关闭走「关闭」按钮 / `Esc`(焦点仍然归还折叠条)。
70
+ - 层级排好:侧边栏 45 < 弹窗 50,toast 提到 70(不会被侧边栏盖住)。
71
+ - **块头、面板标题行整条都是抓手**(不再只靠那个 10px 的 `⠿`):hover 给浅底、`⠿` 平时淡显
72
+ hover 变实、光标 `cursor: grab`——三件事一起说「这行能拖」。块体(输入区)仍然不能拖,
73
+ 否则选词会变成搬块;面板行内按钮也不会触发拖动。
74
+
75
+ ### Fixed
76
+
77
+ - **拖块不再「卡手」:改成插入式排序**(用户反馈「只能显示在可以显示的位置」)。
78
+ 网格里全宽块只有 `x=0` 一个落点、半宽块只有两个,用引擎的自由拖拽时块只能在「合法格」
79
+ 之间跳,还会被纵向压实实时吸回去——拖到哪儿都不对。现在:
80
+ - 拖动只表达**顺序**:拖到某块的上/下半,出现一条 2px 插入线(与表格搬行同一套语义),
81
+ 松手按新顺序重新铺格(`repackBlocks`,**保留每块原来的宽度**)。
82
+ - 宽度仍是独立自由度:拖块的左右边缘改宽,所以「单列变多列」照旧成立。
83
+ - 拖动过程中不再有实时重排(引擎的 drag 已关掉),落点所见即所得。
84
+ - **「拖拽有点难触发」的真因**:上一版把 `Block` 组件直接当成网格的子元素,而
85
+ `react-grid-layout` 靠 `cloneElement` + `ref` 给子元素注入定位与拖拽手柄——函数组件接不住
86
+ ref,按下时抛 `<DraggableCore> not mounted on DragStart`,拖动因此在 mousedown 就断了。
87
+ 现在子元素回到原生 `div`(`.blk-cell`),块放在里面。
88
+
89
+ ## 0.25.0 (2026-11-08)
90
+
91
+ **正文改成「按内容长」的文档:md 有多长就多高,界面去掉边框,读的内容回到第一眼。**
92
+
93
+ 上一版把正文块化了,但块高是固定的:md 正文被塞进一个 220px 的框里滚动——那还是「面板」的思路,
94
+ 不是「读文档」的思路。这一版把高度的决定权还给内容,并顺手把视觉收干净。
95
+
96
+ ### Changed
97
+
98
+ - **块高由内容决定**:行高压到 8px 一档,用 `ResizeObserver` 量块里的内容(md 正文、附件列表、
99
+ 清单/表格、自动增高的 textarea),显示高度 = max(存储的最小高, 内容高度)。内容删掉就缩回最小高。
100
+ - 存的 `h` 因此变成**最小高**:版式 JSON 描述「这个栏目怎么排」,不是「这一条内容有多高」——
101
+ 后者每条记录都不一样,不该落盘(视图期算出来的高度会在回写时剥掉)。
102
+ - **只保留左右缩放手柄**:高度既然跟内容走,再叠一个手动高度只会互相打架(拖宽 → 内容重排 → 高度自己跟)。
103
+ - **md 正文不再限高滚动**:`.md-preview` 去掉 `max-height: 340px / 560px`,阅读态连灰底也去掉,
104
+ 正文直接落在页面上;工具行(字数 / 图片数 / 编辑)退到 hover 才出现。
105
+ - **无边框**:新增 `--line`(rgba 8.5%)代替原来的实色分隔线;块与面板去掉边框与底色,
106
+ 靠留白与字号分层;把手、字段 key、面板操作平时不出现,hover / 聚焦才浮出。
107
+ - **现代控件**:输入框改成「无边框 + 浅底,focus 才显边界」;按钮/标签去掉浮雕投影,改为平色块 + hover 加深;
108
+ 顶部 tab 扁平化(选中只加深底色);浮层统一一层柔和投影。
109
+ - **阅读优先的排版**:条目名抬到标题级(16px / 600,仍是就地可改的输入);
110
+ 「+ 直接加字段」收成折叠项,不再常驻一行;块内正文字号 13px(工具条仍是 12px)。
111
+
112
+ ### Notes
113
+
114
+ - 版式 JSON 的 `h` 语义变了(最小高 / 8px 一档),但**结构与读写口径不变**:
115
+ `section get --json` / `section update --layout` 照旧;老版式读进来会被夹到最小高,
116
+ 视觉上相当于「内容多长就多长」,不需要迁移脚本。
117
+ - 全局样式改动波及所有页面(条目 / 栏目 / 附件 / 导出 / 设置),逐页截图确认过没有退化。
118
+
119
+ ## 0.24.0 (2026-11-08)
120
+
121
+ **条目正文块逻辑化:一个分组 = 一块面板,一个字段 = 面板里的一个块——能拖、能并排、窄屏自动折列。**
122
+
123
+ 过去正文是「分组小标题 + 自动填充的字段矩阵」:顺序由字段字典决定,想把「岗位」挪到「公司名称」
124
+ 右边、想让两块并排、想让长文独占一整行,都只能去改字段字典。现在**版式是独立的一份 JSON**
125
+ (`section.layout`),拖动即改、自动落盘,与「字段值」是两条保存链——拖一下版式不会把没填完的值一起提交。
126
+
127
+ ### Added
128
+
129
+ - **块面板**:每组一块面板,面板里每个字段一个块(块头 = 标签 + 密文/填写策略标签 + 字段 key)。
130
+ 底层用 `react-grid-layout`(MIT)做网格引擎:拖块头 `⠿` 搬块、拖右下角改宽高、单列变多列、
131
+ 窄屏按断点自动折成一列。碰撞 / 回弹 / 折行都交给引擎,**不自己实现面板引擎**——
132
+ 这一层最容易出错,也最没有复用价值。
133
+ - **版式单独存**(`section.layout`):
134
+ `{v, mode, panels[], groups{分组:{collapsed, layouts{断点:[{i,x,y,w,h}]}}}}`。面板顺序、折叠态、
135
+ 每个块的位置/宽度/高度都在里面,`i` 就是字段 key;坐标是列单位(lg 12 / md 10 / sm 6 / xs 4 / xxs 2)。
136
+ - **结构变了版式自愈**(幂等,读写两条路都过一遍):删字段 → 对应块消失且不留空位;加字段 →
137
+ 接到该组最底部(不插队打散已排好的版式);删分组整块移除、新分组接末尾。
138
+ 没存过版式 = 按默认编排铺一遍(窄字段半列、宽字段整行、窄屏单列)。
139
+ - **CLI 对等**:`section get --json` 带出**已按当前字段对齐**的 `layout`;
140
+ `section update --layout @layout.json` 整体写版式、`--clear-layout` 回到默认编排。
141
+ 端到端冒烟把「面板走的那条 HTTP PATCH」与 CLI 两头都钉住了。
142
+ - **落位吸附**:半宽的块从「缝上」(x=3 这种)吸附到 0 / 6 这类档位,两块并排才齐;
143
+ 用户特意拉的自由宽度(除不尽列数,如 5 列)保持原样,不被「自动摆正」。
144
+ - **表单视图**:一键切回单列流式(长文 `md` / 表格字段多时更好读),版式保留、切回来即恢复。
145
+
146
+ ### Notes
147
+
148
+ - 新增依赖 `react-grid-layout@^2.3.0`(含 `react-draggable` / `react-resizable`);构建产物 +约 105KB gzip
149
+ 在条目视图的懒加载分片里,首屏不受影响。
150
+ - 版式**不影响**条目值、完整度与 CLI 读写口径;老 store 没有 `layout` 也能直接用(读一次就按默认编排渲染)。
151
+
5
152
  ## 0.23.1 (2026-11-05)
6
153
 
7
154
  ### Added
package/README.md CHANGED
@@ -11,13 +11,26 @@ nx-sk serve # 打开面板(http://127.0.0.1:7866)
11
11
 
12
12
  ## 安装
13
13
 
14
+ **要求 Node.js >= 18.17。** 包内是构建好的 `dist/`(纯 JS),运行时不需要 TypeScript。
15
+
16
+ ```bash
17
+ npm i -g nx-sk # 命令名就是包名
18
+ nx-sk version
19
+ nx-sk serve # 打开面板
20
+ ```
21
+
22
+ ## 开发
23
+
24
+ **开发/测试需要 Node >= 22.18**(单测用 `node --test` 直接跑 `.ts`,依赖 Node 原生类型剥离);
25
+ 运行 CLI 与面板仍只需 >= 18.17。改了后端要重新构建——`dist/` 是运行时唯一入口。
26
+
14
27
  命令名就是包名。**推荐做成全局垫片**(全局 node_modules 里是指向本项目的软链,
15
28
  改代码立刻生效,不用重新安装):
16
29
 
17
30
  ```bash
18
31
  cd <本项目>
19
32
  pnpm install # 只为构建面板;CLI 侧零依赖
20
- pnpm run build # 产出 src/web/public,serve 才有面板可服务
33
+ pnpm run build # tsc → dist/**/*.js,vite → dist/web/public
21
34
  npm link # → 全局多了 nx-sk / nx-sk.cmd / nx-sk.ps1
22
35
  nx-sk version # 从任何目录都能跑
23
36
  nx-sk serve # 打开面板
@@ -45,7 +58,7 @@ nx-sk serve # 打开面板
45
58
  >
46
59
  > 别忘了 `chmod +x ~/.local/bin/nx-sk`。验证:`cd / && nx-sk version`。
47
60
 
48
- > 不想污染全局也行:`node bin/nx-sk.mjs <子命令>` 等价,或 `npx --no-install . <子命令>`。
61
+ > 不想污染全局也行:先 `pnpm build`,再 `node bin/nx-sk.mjs <子命令>`,或 `npx --no-install . <子命令>`。
49
62
 
50
63
  ### skill:用**软链垫片**(推荐)
51
64
 
@@ -516,9 +529,9 @@ pnpm test # = lint + build + 冒烟 + 单测
516
529
  | --- | --- |
517
530
  | `bin/nx-sk.mjs` | 唯一入口,只转发 argv + 兜异常 |
518
531
  | `src/core/` | 零业务语义:paths / errors / store / fsx / crypto / vault / fields / attachments / render / ids / open |
519
- | `src/modules/<域>/` | `index.js`(action 声明) + `service.js`(业务) + `view.jsx`(面板) |
532
+ | `src/modules/<域>/` | `index.js`(action 声明) + `service.js`(业务)。**不声明视图** |
520
533
  | `src/runtime/` | registry / spec / cli / api / server —— 装配层,不写业务 |
521
- | `src/web/frontend/` | React 壳 + 组件 + 视图注册表 |
534
+ | `src/web/frontend/` | React 壳 + 组件 + `modules/<域>/view.jsx` + 视图注册表(视图只住这里) |
522
535
  | `assets/nx-sk/` | 随包分发的 skill(SKILL.md + references/) |
523
536
  | `tests/unit/` · `tests/smoke.mjs` | 一致性断言 + 端到端冒烟 |
524
537
 
@@ -553,7 +566,7 @@ agent 手册** —— 它写在创建本项目的脚手架 skill `server-cli-web
553
566
  | 把打码值当成新值写回去 | 密钥字段被写成 `sk-1****cdef` 这串掩码,**原凭据永久丢失** | 写入前比对掩码:相等就当成「未改动」保留原密文;含 `******` 的值直接拒绝 |
554
567
  | 删条目/栏目没有留退路 | 误删即不可逆;这是用户的**唯一一份**个人信息 | 破坏性写操作一律先 `transfer export` 导出,导出的包就是唯一回退手段 —— 不另设本地备份模块 |
555
568
  | store.json 解析失败就当空库继续 | 下一次写入把用户数据永久覆盖 | 先把损坏文件改名为 `store.json.corrupt-<时间>.json`,再降级返回空结构 |
556
- | 前端 import 了 `node:*` 或 `core/` | Vite 把 Node 侧代码打进浏览器包,dev 模式白屏 | lint 的 `files` 同时覆盖 `view.jsx`,禁掉 `node:*` / `core` / `runtime` / `service.js` |
569
+ | 前端 import 了 `node:*` 或 `core/` | Vite 把 Node 侧代码打进浏览器包,dev 模式白屏 | lint 的 `files` 覆盖整个 `src/web/frontend/**`(视图都在这里),禁掉 `node:*` / `core` / `runtime` / `service.js` |
557
570
  | 新增模块忘了往 eslint 互依禁列里补一行 | **静默**:新模块变成「谁都可以依赖」,且规则随模块数增加持续衰减 | 在禁列旁留注释说明它是**逐模块枚举**的、漏补是静默的;加模块前先读 `eslint.config.js` |
558
571
  | 代理前缀 `/api` 与前端源码目录 `api/` 撞车 | 只在 dev 模式整页白屏,报错却是 404 / ECONNREFUSED,离根因很远 | vite 代理加 `bypass`,按「是不是前端资源」分流;并用测试钉住 `shouldServeLocally` |
559
572
  | 把「上传 / 选择」放到附件中心,再让条目去引用 | 条目里看不到上传入口,用户不知道该在哪儿加文件;附件中心还被迫预定义分类与槽位,跟栏目脱节 | **上传 / 选择发生在栏目条目里**(`file` 字段的值 = 附件 id);附件中心由栏目驱动、只做统一展示与库存管理,**不预定义分类、不做指派** |
@@ -193,6 +193,11 @@ nx-sk file open f_xxxx # 系统默认程序打开原件
193
193
  - 附件中心:**「清理未引用(N)」**一键删掉所有没人引用的附件(判据与「未引用」筛选片同一套);
194
194
  - 条目表单:**保存按钮固定在右上角**,有改动时顶栏出现「N 处改动未保存 · 保存」,
195
195
  长栏目滚到一半也看得见(底部也还有一个)。
196
+ - **正文是块面板**:一个分组一块面板、一个字段一块,拖块头整行换顺序、拖左右边缘改宽(可并排成多列);
197
+ 高度**跟内容走**(md 正文/附件/清单有多长就多高,不在块内滚);版式(`section.layout`)自动保存,
198
+ 与字段值两条保存链。想用命令改版式:
199
+ `nx-sk section get <栏目> --json`(读,已按当前字段对齐)/ `section update <栏目> --layout @layout.json`
200
+ / `section update <栏目> --clear-layout`(回默认编排)。形状与自愈规则见 [[10-sections-entries]] 第六节。
196
201
 
197
202
  求职条目里的「简历附件 / 头像图片 / 成绩单 / 证书扫描件」是 `file` 字段,
198
203
  它的值是**附件 id 的列表**——一个字段可以挂**多个**附件(简历多版本、头像多张)
@@ -278,7 +283,7 @@ nx-sk source update <条目> --id <itemId> --mapped 字段key # 标注「这
278
283
  nx-sk source ensure # 给老栏目补上这个字段(模板是后加的,老栏目没有)
279
284
  ```
280
285
 
281
- 面板上:条目表单底部一条**折叠条**「原始资料 · N 条」,点开是右侧抽屉(左清单、中正文),
286
+ 面板上:条目表单底部一条**折叠条**「原始资料 · N 条」,点开是右侧**侧边栏**(左清单、中正文;不遮不挡,左边条目还能读),
282
287
  文件可拖进去(也给了「选择文件」按钮与粘贴框——拖拽不能是唯一入口,WCAG 2.2 AA)。
283
288
  三条纪律:**原文不改写**、**不确定就留空**、**敏感内容不进袋**(袋是明文的;
284
289
  密码走场景栏目的密文字段,不进袋)。
@@ -7,7 +7,7 @@
7
7
  ```
8
8
  store.json(~/nx-sk/store.json 或 $NX_SK_STORE)
9
9
  ├─ settings 全局配置(可读 JSON/Markdown 导出见 60-transfer)
10
- ├─ sections[] 栏目:id / title / order / template / titleField / groups[] / fields[]
10
+ ├─ sections[] 栏目:id / title / order / template / titleField / groups[] / fields[] / layout
11
11
  └─ entries[] 条目:id(e_xxx) / section / title / tags[] / values{字段key: 值} / 时间戳
12
12
  ```
13
13
 
@@ -171,7 +171,8 @@ nx-sk section update job --fields @fields.json
171
171
  > 还能有**计算列**(JS 表达式)与**表尾统计**。最根本的差别是**结构归数据**:列定义、行、公式、
172
172
  > 统计配置全部存在**条目值里**(`{v, columns, rows, totals?, summary?}`),字段定义里的 `columns`
173
173
  > 只是「新建时的初始列」。所以「这一条想多一列」不必改 schema,也不影响同栏目其它条目。
174
- > 面板上**行与列都能拖**:拖表头搬列、拖行尾的 ⠿ 搬行(松手前有插入线)。
174
+ > 面板上**列头有 ⋮ 属性菜单**(改名 / 换类型 / 单位 / 汇总 / 默认值 / 密文 / 选项 / 公式 + 左右插入 / 复制 / 删除),
175
+ > 表头末尾有 `+` 加列;**行与列都能拖**(拖表头搬列、拖行左缘的 ⠿ 搬行,松手前有插入线)。
175
176
  >
176
177
  > ```bash
177
178
  > nx-sk section update mysec --add-field '{"key":"tasks","label":"任务表","type":"table"}'
@@ -187,7 +188,7 @@ nx-sk section update job --fields @fields.json
187
188
  > 杂乱资料(邮件、截图、PDF 摘录、手抄的表)先**原样**丢进袋里,之后由 agent 或人
188
189
  > 按字段字典整理进上面的字段——「先乱入、后整理」因此成立:没有这个袋子,用户只有
189
190
  > 把乱数据硬塞进结构化字段(把结构搞脏)或干脆不存(信息丢了)两个坏选择。
190
- > - 面板上:条目表单底部一条**折叠条**「原始资料 · N 条」,点开是右侧抽屉——
191
+ > - 面板上:条目表单底部一条**折叠条**「原始资料 · N 条」,点开是右侧**侧边栏**(不遮不挡,左边条目还能读)——
191
192
  > 左清单、中间正文,文件可拖进去(也给了「选择文件」按钮与粘贴框:拖拽不能是唯一入口)。
192
193
  > - CLI:`nx-sk source list / get / add / update / remove / ensure`(细节见 `sk-source` skill)。
193
194
  > - 三条纪律:**原文不改写**(整理结果写进字段或新的一条)、**不确定就留空**、
@@ -272,11 +273,52 @@ nx-sk entry get 张三
272
273
 
273
274
  `nx-sk serve` → 每个栏目一个 tab,共用同一个条目视图:
274
275
 
275
- - 左侧条目列表(带完整度),右侧按分组渲染的字段表单;
276
+ - 左侧条目列表(带完整度),右侧**按分组渲染的块面板**:一个分组 = 一块面板,一个字段 = 面板里的一个块;
277
+ - **块布局(默认)**:拖**块头整行**(不用对准 ⠿)**换顺序**——拖到某块的上/下半就插到它前面/后面;
278
+ 拖块的**左右边缘**改宽度(单列可变多列);拖**面板标题行**换面板顺序。
279
+ 块可以并排,窄屏自动折成一列(响应式由网格引擎按断点算,不是手写媒体查询)。
280
+ **高度跟内容走**:`md` 正文、附件列表、清单表格有多长就多高,不在块内滚(阅读优先);删掉内容再缩回最小高。
281
+ **版式改动自动保存**,与字段值的「保存」互不影响——拖一下版式不会把没填完的值一起提交;
282
+ - **表单视图**:同一份数据按字段顺序单列铺开(长文 `md`、`table` 字段多时更好读);切回块布局版式原样恢复;
276
283
  - **「栏目」页的「编辑」弹窗能改整个结构**:分组可新增 / 改名(含换 id)/ 上下挪 / 删(空的才让删),
277
284
  字段可改标签、类型、分组、提示、必填策略、组内上下挪、删除 —— 一次改动一次落盘;
278
285
  - 密文字段默认显示原文;「打码显示(投屏/截图时用)」勾选等价于 `--mask`(选择持久化在 localStorage);
279
286
  - 保存是 **PATCH**:只有被你改动过的字段才会发出去(面板会显示「N 处改动」);
280
287
  - 每个页面底部会派生显示该模块的 CLI 等价命令(可点击复制)。
281
288
 
289
+ ### 版式(`section.layout`):**显示关系单独存**
290
+
291
+ 「有哪些字段」(字段字典)与「这些块怎么摆」(版式)是两件事,所以版式是栏目上**另一份 JSON**:
292
+
293
+ ```json
294
+ {
295
+ "v": 1,
296
+ "mode": "grid", // grid = 面板块 · flow = 单列表单
297
+ "panels": ["basic", "contact"], // 面板(分组)顺序
298
+ "groups": {
299
+ "basic": {
300
+ "collapsed": false,
301
+ "layouts": { // 按断点各一份,坐标是**列单位**
302
+ "lg": [{ "i": "name", "x": 0, "y": 0, "w": 6, "h": 2 }]
303
+ }
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ - `i` = 字段 key;`x / y / w / h` 是列单位(`lg` 12 列、`md` 10、`sm` 6、`xs` 4、`xxs` 2)。
310
+ `h` 是**最小高**(一档 8px + 10px 间距):实际显示高度 = max(最小高, 内容量到的高度),
311
+ 所以同一份版式套在不同内容上都能读。底层是 `react-grid-layout` 的网格形状,面板只管喂 JSON;
312
+ 碰撞、回弹、折列都由引擎算。
313
+ - **结构变了版式自愈**(幂等,读写两条路都过一遍):删字段 → 对应块消失且不留空位;
314
+ 加字段 → 接到该组最底部(不插队打散已排好的版式);删分组 → 整块移除;新分组 → 面板接在末尾。
315
+ - 没存过版式 = 按默认编排铺一遍(窄字段半列、宽字段整行,窄屏单列),**不是**没有版式。
316
+ - 版式不参与完整度、不影响条目值;`flow` 模式下版式仍保留。
317
+
318
+ ```bash
319
+ nx-sk section get job --json # 含 layout(已按当前字段对齐)
320
+ nx-sk section update job --layout @layout.json # 整体写版式(面板拖完回写的就是这条)
321
+ nx-sk section update job --clear-layout # 回到默认编排(不是删成 null)
322
+ ```
323
+
282
324
  面板上没有 CLI 做不到的事——两边共享同一套 action 声明。
@@ -13,11 +13,11 @@
13
13
 
14
14
  | type | 值形状 | 结构(列)归谁 | 面板怎么编 | 读它 |
15
15
  | --- | --- | --- | --- | --- |
16
- | `table` | `{v, columns, rows, totals?, summary?}` | **数据自己**(字段定义只给初始列) | 表格控件 + 「列设置」;**拖表头搬列、拖行尾 ⠿ 搬行** | `entry table` / `entry get` |
16
+ | `table` | `{v, columns, rows, totals?, summary?}` | **数据自己**(字段定义只给初始列) | 表格控件:**列头 ⋮ 属性菜单**、表头 + 加列;拖表头搬列、拖行左缘 ⠿ 搬行 | `entry table` / `entry get` |
17
17
  | `entrylist` | `[{列key: 值}, …]` | 字段字典的 `fields` | 密集迷你表(直接增删行,**拖 ⠿ 调行序**) | `entry get` |
18
18
  | `md` | 一段 markdown 字符串 | — | 图文编辑器(Ctrl+V 贴图) | `entry get`(图片还原成路径) |
19
19
  | `file` | `[附件id, …]` | 字段字典的 `kind` / `maxItems` | 附件选择器(上传 / 从库里选) | `entry get`(附件名 + 物理路径) |
20
- | `source` | `[{id, kind, name, text\|fileId\|url, addedAt, …}]` | 内置(每个栏目自带一个) | 底部折叠条 → 右侧抽屉 | `source get` |
20
+ | `source` | `[{id, kind, name, text\|fileId\|url, addedAt, …}]` | 内置(每个栏目自带一个) | 底部折叠条 → 右侧侧边栏 | `source get` |
21
21
  | `secret` | 一个字符串(落盘是密文) | — | 输入框 | `key get` / `entry get` |
22
22
 
23
23
  共同点:**写入即校验**(不合法就整条拒绝,不会存半条)、**空值有明确语义**、
@@ -120,20 +120,33 @@
120
120
  默认值只在**新增行**时用(面板「添加行」按列的 `default` 预填),不会回头改已有行;
121
121
  字段级的默认是:初始列 + 表尾自动统计(数字 sum / 勾选 countTrue)+ 列类型默认 text。
122
122
 
123
- ### 面板上怎么调顺序(拖)
123
+ ### 面板上怎么改结构、怎么调顺序
124
124
 
125
- 排序是表格最常见的编辑动作之一,所以**行与列都能拖**,不用去点箭头:
125
+ **列管理在列头上**:鼠标移到表头 → 右侧出现 `⋮` → 打开这一列的属性菜单:
126
126
 
127
- - **搬列**:直接拖**表头**(表头里没有输入框,不会跟选词打架);「列设置」里每列前面的 ⠿
128
- 也能拖,效果一样。
129
- - **搬行**:拖行尾的 ⠿。整行 `draggable` 不这么做——那会跟单元格里选文本冲突
130
- (想选一段字结果把行拖走了),所以把手是唯一的拖拽入口。
131
- - 松手前会有一条**插入线**落在上/下半(行)或左/右半(列)的位置:线在哪就插到哪。
132
- 只高亮「这一行」是做不到这件事的。
133
- - ↑↓ / ←→ 保留(鼠标悬停在行上才浮出来),键盘与「就挪一格」的场景仍然走得通;
134
- 行内操作键统一是 16px 的小图标键——密集表里的按钮不该抢内容的注意力。
127
+ | 菜单里能做什么 | 说明 |
128
+ | --- | --- |
129
+ | 列名 / 类型 | 就地改;改成「计算列(JS)」后填公式 |
130
+ | key / 单位 / 默认值 | key 是公式里引用的名字(改 key 会连值一起搬) |
131
+ | 统计 | 表尾这一格怎么算(求和 / 平均 / 计数 / 不统计…) |
132
+ | 密文 | 这一列的值以 AES-256-GCM 密文落盘 |
133
+ | 选项 | `select` 类型的候选项,逗号分隔 |
134
+ | 左移 / 右移 | 挪一格 |
135
+ | 向左 / 向右插入列 | 在它旁边插一列空列 |
136
+ | 复制属性 | 连**值**一起复制出一列(`xxxCopy`) |
137
+ | 删除属性 | 连值一起删(只删定义会留下「没有列的数据」) |
138
+
139
+ 表头**末尾有一个 `+`**:加列就在列该长的位置,不用回头去工具栏找。
140
+
141
+ 排序:行与列都能拖,拖到哪儿有一条**插入线**告诉你会插在哪:
142
+
143
+ - **搬列**:直接拖**表头**(表头里没有输入框,不会跟选词打架)。
144
+ - **搬行**:拖行**左侧沟槽**的 ⠿(hover 时行号会变成 `⋮`(行菜单:上下插入 / 复制 / 删除)+ `⠿`)。
145
+ 整行 `draggable` 不这么做——那会跟单元格里选文本冲突(想选一段字结果把行拖走了)。
146
+ - 表尾有一整行「**新建一行**」:加行不用回工具栏;回车在最后一格也能直接续一行。
147
+ - 行内操作统一是小图标,hover 才出现——密集表里的按钮不该抢内容的注意力。
135
148
 
136
- `entrylist` 的行同理(拖 ⠿),删除键也从「一颗正常大小的按钮」换成了小图标键。
149
+ `entrylist` 的行同理(拖 ⠿),删除键也是小图标键。
137
150
 
138
151
  ### 常见错 → 改法
139
152
 
@@ -173,7 +186,7 @@
173
186
  - 每个非 KV 栏目自带一个:杂乱资料先**原样**入袋(不改写),之后按字段字典整理。
174
187
  - **`text` 的底层是 markdown**——聊天记录图文混排,图和文不能分开存:
175
188
  正文里直接写 `![说明](f_xxx)`(附件库的图)或 `![说明](https://…)`(外链图);
176
- 面板抽屉里图片 Ctrl+V / 拖进编辑器就自动上传并插入引用。
189
+ 面板侧边栏里图片 Ctrl+V / 拖进编辑器就自动上传并插入引用。
177
190
  普通文字本来就是合法 markdown,老数据不用迁移。
178
191
  - 不计完整度(`fill: optional`);袋里是明文,**敏感内容不要进袋**。
179
192
 
@@ -94,7 +94,7 @@ nx-sk source add <条目> --url "https://qcloud.sicau.edu.cn/login"
94
94
  ```
95
95
 
96
96
  - `--file` **默认复制不搬走**原件(这是「加一条资料」,不是 `file add` 的搬运语义)。
97
- - 一次要收很多份:面板里在「原始资料」抽屉中**多选文件**拖进去,或用 `--file` 重复调用。
97
+ - 一次要收很多份:面板里在「原始资料」侧边栏中**多选文件**拖进去,或用 `--file` 重复调用。
98
98
  - 文件分发细则(PDF 怎么拆、截图怎么批量进、表格怎么变成清单行)见 [[20-intake]]。
99
99
 
100
100
  ### 第 2 步:读袋子
@@ -160,7 +160,7 @@ nx-sk section dump <栏目> --json # 完整度与未填清单(袋子
160
160
  ## 面板里怎么用
161
161
 
162
162
  条目表单底部有一条**折叠条**「原始资料 · N 条」(暂存区不该在日常填表时占地方)。
163
- 点开是右侧抽屉:左边清单、中间正文。
163
+ 点开是右侧侧边栏:左边清单、中间正文(不遮挡条目)。
164
164
 
165
165
  - 拖文件进左栏 → 自动上传并入袋
166
166
  - 「+ 选择文件」按钮 → 同样的事(**拖拽不是唯一入口**:WCAG 2.2 AA 要求键盘/单指针也能完成)
@@ -43,7 +43,7 @@ nx-sk source add <条目> --file "D:\资料\用户指南.pdf" --name "平台用
43
43
 
44
44
  ## 批量进一批文件
45
45
 
46
- 面板最快:条目表单底部「原始资料」折叠条 → 抽屉 → 「+ 选择文件」(可多选)
46
+ 面板最快:条目表单底部「原始资料」折叠条 → 右侧侧边栏 → 「+ 选择文件」(可多选)
47
47
  或直接把一批文件拖进左栏。
48
48
 
49
49
  CLI 逐个加(每条一次调用):
package/bin/nx-sk.mjs CHANGED
@@ -1,7 +1,25 @@
1
1
  #!/usr/bin/env node
2
- // nx-sk 的唯一可执行入口:只做两件事——转发 argv、兜住未捕获的异常。
3
- // 所有命令分发都在 src/runtime/cli.js(那里才是命令表)。
4
- import { runCli } from '../src/runtime/cli.js';
2
+ // nx-sk 的唯一可执行入口:转发 argv、兜住未捕获的异常。
3
+ // 所有命令分发都在 dist/runtime/cli.js(那里才是命令表)——只跑构建产物,
4
+ // 不在运行时做 TypeScript 类型剥离(Node 对 node_modules 下的 .ts 直接拒绝)。
5
+ //
6
+ // 首次从源码仓库直接跑、或改了后端还没构建时,dist 不存在:给一句能照做的提示,
7
+ // 而不是把 ERR_MODULE_NOT_FOUND 的栈甩给用户。
8
+ import { existsSync } from 'node:fs';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const cliUrl = new URL('../dist/runtime/cli.js', import.meta.url);
12
+
13
+ if (!existsSync(fileURLToPath(cliUrl))) {
14
+ process.stderr.write(
15
+ 'nx-sk: 找不到构建产物 dist/runtime/cli.js。\n'
16
+ + '源码仓库需要先构建:pnpm build(npm run build)——它会先 tsc 产出 dist,再构建面板。\n'
17
+ + '如果你是用 npm 全局安装的,这说明包不完整,请重装:npm i -g nx-sk。\n',
18
+ );
19
+ process.exit(1);
20
+ }
21
+
22
+ const { runCli } = await import(cliUrl.href);
5
23
 
6
24
  runCli(process.argv.slice(2)).catch((err) => {
7
25
  const message = err && err.message ? err.message : String(err);
@@ -12,8 +12,7 @@
12
12
  // 又会拦住用 --data 写入的合法 CRLF 多行值(高假阳性);LF 截断后则与合法单行值
13
13
  // 完全不可区分。可靠的信号不存在,因此只能提供可靠通道 + 修好通道本身。
14
14
  import { readFileSync } from 'node:fs';
15
- import { badInput } from './errors.js';
16
-
15
+ import { badInput } from "./errors.js";
17
16
  /**
18
17
  * `--data` 的值:JSON 字面量 / `@文件路径` / `-`(stdin)。
19
18
  *
@@ -21,33 +20,34 @@ import { badInput } from './errors.js';
21
20
  * 「读 stdin」因此不是一个无法测试的隐式依赖,也让本模块保持纯函数(不自己碰 fd 0)。
22
21
  */
23
22
  export function parseDataArg(raw, takeStdin) {
24
- const s = String(raw ?? '').trim();
25
- if (!s) throw badInput('--data 需要一个 JSON 字面量、@文件路径,或 -(从 stdin 读)');
26
- if (s === '-' || s === '@-') return parseJson(takeStdin(), '-');
27
- if (s.startsWith('@')) return parseJson(readText(s.slice(1)), s.slice(1));
28
- return parseJson(s, '--data'); // 直接给的字面 JSON
23
+ const s = String(raw ?? '').trim();
24
+ if (!s)
25
+ throw badInput('--data 需要一个 JSON 字面量、@文件路径,或 -(从 stdin 读)');
26
+ if (s === '-' || s === '@-')
27
+ return parseJson(takeStdin(), '-');
28
+ if (s.startsWith('@'))
29
+ return parseJson(readText(s.slice(1)), s.slice(1));
30
+ return parseJson(s, '--data'); // 直接给的字面 JSON
29
31
  }
30
-
31
32
  /** 值本身走 stdin 的入口(`key set NAME -`)。true 表示「这个值来自 stdin」。 */
32
33
  export function parseStdinValue(raw) {
33
- return String(raw ?? '').trim() === '-';
34
+ return String(raw ?? '').trim() === '-';
34
35
  }
35
-
36
36
  function parseJson(text, src) {
37
- try {
38
- return JSON.parse(text);
39
- } catch (e) {
40
- throw badInput(`--data 不是合法 JSON(${src}): ${e.message}`);
41
- }
37
+ try {
38
+ return JSON.parse(text);
39
+ }
40
+ catch (e) {
41
+ throw badInput(`--data 不是合法 JSON(${src}): ${e.message}`);
42
+ }
42
43
  }
43
-
44
44
  function readText(p) {
45
- try {
46
- return readFileSync(p, 'utf8');
47
- } catch (e) {
48
- // 路径写法是最常见的坑:Git Bash 的 /tmp 在 node 侧看不到(真实事故 ENOENT),
49
- // 所以错误里直接把 Windows 路径形式写出来,省掉一轮排查。
50
- throw badInput(`读取文件失败: ${p}(${e.message})—— Windows 上请用 C:/Users/... 形式的路径`);
51
- }
45
+ try {
46
+ return readFileSync(p, 'utf8');
47
+ }
48
+ catch (e) {
49
+ // 路径写法是最常见的坑:Git Bash 的 /tmp 在 node 侧看不到(真实事故 ENOENT),
50
+ // 所以错误里直接把 Windows 路径形式写出来,省掉一轮排查。
51
+ throw badInput(`读取文件失败: ${p}(${e.message})—— Windows 上请用 C:/Users/... 形式的路径`);
52
+ }
52
53
  }
53
-