@wscytz/dsl 0.0.0-stage → 0.1.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/LICENSE +201 -0
- package/README.md +81 -2
- package/dist/canvas-dsl.d.ts +62 -0
- package/dist/canvas-dsl.js +215 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +389 -0
- package/dist/dsl-compute.d.ts +48 -0
- package/dist/dsl-compute.js +248 -0
- package/dist/dsl-grammar.d.ts +134 -0
- package/dist/dsl-grammar.js +227 -0
- package/dist/dsl-parser.d.ts +199 -0
- package/dist/dsl-parser.gen.d.ts +4 -0
- package/dist/dsl-parser.gen.js +3365 -0
- package/dist/dsl-parser.js +683 -0
- package/dist/dsl-relational-solver.d.ts +47 -0
- package/dist/dsl-relational-solver.js +221 -0
- package/dist/dsl-sanitize.d.ts +56 -0
- package/dist/dsl-sanitize.js +257 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/routing.d.ts +73 -0
- package/dist/routing.js +617 -0
- package/dist/to-json-canvas.d.ts +74 -0
- package/dist/to-json-canvas.js +149 -0
- package/dist/to-svg.d.ts +48 -0
- package/dist/to-svg.js +542 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +12 -0
- package/package.json +53 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 cy
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @wscytz/dsl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
cys-dsl — cy's Stift 画布的**双向文字表示(转义)**。整张画布能压成一段文本,文本也能重建/改画布;任何 AI 或外部工具读写一段文本就能操作画布。
|
|
4
|
+
|
|
5
|
+
## 是什么
|
|
6
|
+
|
|
7
|
+
cys-dsl **v9** 文法:**5 种元素**(`card`/`rect`/`frame`/`text`/`arrow`)、6 色名(legacy 枚举冻结)、**关系式放置**(`right-of`/`below #锚` + `@gap`;v9 起 `@组名` 组级相对锚——锚定组包围盒,支持 `@"含空格名"` 与中文裸名)、箭头签名(label/color/dash/arrowhead/route/wikilink)、**卡片内容 `@title`(短)/`@content`(长 markdown,`\n` 转义多行)**,以及语义 directive:**`@group("名")`**(v7,语义分组)、**`@href(#a;#b)`**(v7,显式引用)、**`@compute("公式")`**(v7,安全公式,仅 text)、**`@type/@tags/@links/@code/@quote`**(v8,卡片结构化字段)。v5 内容;v6 freedraw 出 DSL;v9 组级相对锚(锚位三形态与保留字防线见 `DSL_GRAMMAR_REFERENCE`)。
|
|
8
|
+
|
|
9
|
+
## public API
|
|
10
|
+
|
|
11
|
+
- **grammar**:`DSL_VERSION`、`DSL_KINDS`、`DSL_COLORS`、`DSL_COLOR_ALIASES`、`DSL_GRAMMAR_REFERENCE`、`DSL_RESERVED_GROUP_ANCHOR_WORDS`(v9 组锚保留字)、`DSL_MAX_TEXT_LEN`、`DSL_MAX_CONTENT_LEN`、`truncateDslText`(代理对安全截断)
|
|
12
|
+
- **serialize**:`serializeCanvas`、`serializeCanvasReadable`、`serializeElement`
|
|
13
|
+
- **parse**:`parseDsl`、`parseDslWithDiagnostics`、`parseDslStrictWithDiagnostics`(Peggy 语法驱动)
|
|
14
|
+
- **sanitize**:`sanitizeDslOps`(opt-in 修正层,防 LLM 产非法值不崩)
|
|
15
|
+
- **solve**:`solveRelational` + `ExistingGeom`/`SolveResult`(关系式 → 绝对坐标;组锚、碰撞避让、断链诊断)
|
|
16
|
+
- **adapter**:`toJsonCanvas`(→ JSON Canvas 1.0;integer 坐标、id 去重、跳过留痕)+ `JsonCanvasFile/Node/Edge/SkippedOp` 类型
|
|
17
|
+
- **svg**:`toSvg`(零依赖预览级 SVG,CLI `render --svg` 的底层)
|
|
18
|
+
- **types**:`CanvasElement` 等边界类型(与 canvas-engine/domain 结构兼容,`types-compat.test.ts` 钉住)
|
|
19
|
+
|
|
20
|
+
## 用法
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { serializeCanvas, parseDsl, sanitizeDslOps, solveRelational, DSL_GRAMMAR_REFERENCE } from '@wscytz/dsl'
|
|
24
|
+
|
|
25
|
+
const text = serializeCanvas(elements) // 画布 → 文本
|
|
26
|
+
const ops = parseDsl(text) // 文本 → ops
|
|
27
|
+
const clean = sanitizeDslOps(ops, ctx) // 修正非法值(永不抛错)
|
|
28
|
+
const solved = solveRelational(clean.ops) // 关系式 → 绝对坐标
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## CLI
|
|
32
|
+
|
|
33
|
+
`pnpm --filter @wscytz/dsl cli` 或 `tsx src/cli.ts <命令> <file.sift>`(npm 发布后为 `npx`;后缀 `.sift` 为约定,任意文本文件可读):
|
|
34
|
+
|
|
35
|
+
- `validate` — 语法/卫生/求解全链检查;诊断走 stderr,警告不阻断(exit 0)/ 语法错 exit 1 / 用法错 exit 2。
|
|
36
|
+
- `render` — 默认输出 JSON Canvas 1.0(Obsidian `.canvas` 直接可开);`--svg` 输出预览级 SVG。SVG 侧:默认路由**自动避障**(正交可见图 + 方向状态 A* 求解器规划,free 模式叠视线拉直;生成路径不穿卡;显式 `@route/@curve/@elbow` 不干预;源/目标被围死退启发式兜底);`@route(elbow)` 未给折点 = 正交自动路由(求解器,严格水平/垂直);折线转角圆角化。求解器参数可调(`RoutingConfig`:安全边距/折代价 λ/量化步长等,见 `src/routing.ts`)。
|
|
37
|
+
- `roundtrip` — DSL→元素→DSL 双往返签名比对(转义保真)。
|
|
38
|
+
|
|
39
|
+
## 依赖
|
|
40
|
+
|
|
41
|
+
**零运行时依赖**(2026-10-24 剥离定案):src/ 只 import 包内模块与 Node 内建(cli.ts 的 `node:fs`/`node:url`,不进 barrel)。原 `@cys-stift/canvas-engine`/`@cys-stift/domain` 类型依赖已改为包内 `types.ts` 边界副本 + 结构兼容测试(`types-compat.test.ts`);二者仅作为 devDep 供测试比对。框架无关,可独立于本 monorepo 复用。
|
|
42
|
+
|
|
43
|
+
## 文法治理
|
|
44
|
+
|
|
45
|
+
- 增删指令种类 / 属性 / 颜色枚举 → bump `DSL_VERSION`(`dsl-grammar.ts`)。
|
|
46
|
+
- 改 Peggy 语法后跑 `pnpm --filter @wscytz/dsl gen` 重新生成 parser。
|
|
47
|
+
- round-trip 契约由 `__tests__/` 守护(roundtrip / v4-stability / robustness)。
|
|
48
|
+
|
|
49
|
+
## 不变量
|
|
50
|
+
|
|
51
|
+
- **round-trip**:活跃 kind 的 serialize↔parse 双向稳定。
|
|
52
|
+
- **sanitize 永不抛错**:坏输入降级,不整块丢。
|
|
53
|
+
- **freedraw 不在 DSL**:freedraw 已出 DSL(程序自管 R2 + 渲染);serialize 按 `DSL_KINDS` 过滤,`[freedraw]` 行 parse 报 unrecognized。点序列/位置都不进 DSL(隐私 + 防 DoS + 存储重/意义低)。
|
|
54
|
+
- **Bauhaus 6 色**:不新增色。
|
|
55
|
+
- **截断代理对安全**:`truncateDslText` 不劈开 emoji/增补平面字符(不产孤立代理位)。
|
|
56
|
+
|
|
57
|
+
## 内容能力与边界(v7;内容自 v5 引入)
|
|
58
|
+
|
|
59
|
+
v5 的卡片内容(`@title`/`@content`)在格式层、apply 层、`/ask` 落库层均已接通(无遗留适配缝)。A/D/E 三处此前的缺口/局限于 2026-07-22 全部闭合:
|
|
60
|
+
|
|
61
|
+
- **A(`/ask` 落库)**:`applyOpsAndPersist` 建/改卡写回 `@title`/`@content`(create 不再落空标题卡;update 经 post-hoc 回写写回 `Card.title/body`;回滚 + 一次性 undo 覆盖内容);companion live 路径(`makeOnCardCreate`/`makeOnCardUpdate`)同接。锁于 `apps/web` 的 `canvas-host-builder.test.ts`。
|
|
62
|
+
- **D(清空内容)**:`@title("")`/`@content("")` parse 成空串 → apply 真写空串清空(锁于 `dsl-content.test.ts`)。serialize 对空串**不发** token(空=默认态,by-design)。
|
|
63
|
+
- **E(纯内容/属性编辑)**:现有卡的 `@title`/`@content`/`@color`/`@size` 编辑**可省 `@pos`**(`keepExistingPos` 标志,apply 时几何沿用现有卡);`@pos` 仅在**移动**卡或**建卡**时必需。裸行(无任何字段)/ `create` 无 `@pos` 仍报 `missing @pos`。锁于 `dsl-content.test.ts` + `apply-content.test.ts`。
|
|
64
|
+
- DSL 模态编辑器(人读)与 `/ask` agent(AI 写)两条路径对 `@title`/`@content` 行为一致。
|
|
65
|
+
|
|
66
|
+
**边界(by-design,非局限)**:serialize 不发空 token(空是默认态,无需冗余);`keepExistingPos` 是**输入专用**——serialize 始终发绝对 `@pos`,故 round-trip 序列化格式不变(未 bump `DSL_VERSION`)。
|
|
67
|
+
|
|
68
|
+
## 预期能力测试(qwen-max-7-21)
|
|
69
|
+
|
|
70
|
+
`__tests__/qwen-max-7-21-*.test.ts` 是本包自带的**预期能力测试集**(与具体 LLM 解耦的基线 + 可选实弹):
|
|
71
|
+
|
|
72
|
+
- `qwen-max-7-21-roundtrip`:完整文字化——整张画布(几何 + 内容)无损往返。
|
|
73
|
+
- `qwen-max-7-21-ai-grammar`:AI 可驱动·文法侧——真实风格 LLM 输出样本零诊断可解析 + sanitize 不抛。
|
|
74
|
+
- `qwen-max-7-21-ai-apply`(在 `apps/web`):AI 可驱动·应用侧——AI 重排 DSL 经 `applyLayout` 真结构化画布。
|
|
75
|
+
- `qwen-max-7-21-live-llm`:**live 实测**(env 门控,默认跳过)——真调 LLM(DeepSeek `deepseek-chat` / `deepseek-reasoner`,或 GLM 兜底)只凭语法参考产出合法 DSL。启用:
|
|
76
|
+
`CYS_DSL_LIVE_LLM=1 DEEPSEEK_API_KEY=… pnpm --filter @wscytz/dsl test qwen-max-7-21-live-llm`。
|
|
77
|
+
prompt 仅含合成画布;key 只进 auth header,不入日志/提交。
|
|
78
|
+
|
|
79
|
+
## 相关
|
|
80
|
+
|
|
81
|
+
- 论证与 v5 内容扩展计划:私有仓 `docs/research/2026-07-21-dsl-v5-content-rationale.md`、`docs/plans/2026-07-21-dsl-execution.md`。
|
|
82
|
+
- 提取先例:`packages/canvas-engine`(同款"解耦 + 重新打包"打法)。
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas DSL — the unified bidirectional text format for the canvas (Phase 0 / T3).
|
|
3
|
+
*
|
|
4
|
+
* `serializeCanvas(elements)` is the **canvas → text** direction (the missing
|
|
5
|
+
* half before Phase 0). The **text → canvas** direction is `parseDsl` (in
|
|
6
|
+
* ./dsl-parser.ts); together they round-trip the active geometric kinds.
|
|
7
|
+
*
|
|
8
|
+
* Grammar (active kinds only; see CanvasElementKind active/legacy split):
|
|
9
|
+
* [card #<id>] @pos(<x>,<y>) @size(<w>,<h>) @color(<c>)
|
|
10
|
+
* [rect #<id>] @pos(<x>,<y>) @size(<w>,<h>) @color(<c>)
|
|
11
|
+
* [text #<id>] @pos(<x>,<y>) @text("<t>") @color(<c>)
|
|
12
|
+
* [arrow #<id>] from #<a> to #<b> @label("<l>") @color(<c>) @dash(solid|dashed|dotted) @arrowhead(arrow|triangle|none)
|
|
13
|
+
*
|
|
14
|
+
* Legacy kinds (ellipse/line/note/image) and freedraw are NOT serialized — they're
|
|
15
|
+
* not in DSL_KINDS. freedraw is program-managed (R2 store + renderer): point sequence
|
|
16
|
+
* is heavy / low-value / privacy-sensitive, so it stays out of the text format entirely.
|
|
17
|
+
* Canonical single source: ./dsl-grammar.ts (DSL_KINDS / DSL_COLORS / DSL_GRAMMAR_REFERENCE).
|
|
18
|
+
*
|
|
19
|
+
* 已知限制(2026-08-29 审计登记):rotation 不序列化 —— DSL 文本路径只往返
|
|
20
|
+
* x/y/w/h。当前渲染器不消费 rotation(self-built-render 零引用)、无任何 UI 手柄
|
|
21
|
+
* 产出非零值,DB 路径(canvas-binding)保留字段;若将来引入旋转手柄,须同步在
|
|
22
|
+
* grammar 加 @rotation 指令(加法,bump DSL_VERSION),否则 DSL Apply 会把旋转归零。
|
|
23
|
+
*/
|
|
24
|
+
import type { CanvasElement, CardType, TagRef, LinkPreview, CodeBlock, Quote } from './types.js';
|
|
25
|
+
/**
|
|
26
|
+
* v8:card 行可注入的卡片内容/结构化字段(消费者从 CardService 读,经 resolve 回调传入)。
|
|
27
|
+
* title/content 是 v5;type/tags/links/codeSnippets/quotes 是 v8。media 故意不在(二进制重/隐私,
|
|
28
|
+
* 不进 DSL)。缺省几何-only(不传 resolve → 所有现有调用点零改动,向后兼容)。
|
|
29
|
+
*/
|
|
30
|
+
export type CardDslContent = {
|
|
31
|
+
title?: string;
|
|
32
|
+
content?: string;
|
|
33
|
+
type?: CardType;
|
|
34
|
+
tags?: TagRef[];
|
|
35
|
+
links?: LinkPreview[];
|
|
36
|
+
codeSnippets?: CodeBlock[];
|
|
37
|
+
quotes?: Quote[];
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Serialize the canvas's active elements to a text block the AI can read.
|
|
41
|
+
* Pure function of the element list — no side-effects, no engine access.
|
|
42
|
+
*/
|
|
43
|
+
export declare function serializeCanvas(elements: CanvasElement[],
|
|
44
|
+
/** v5:可选,card id → 卡片内容(v8:含结构化字段,见 {@link CardDslContent})。
|
|
45
|
+
* 不传 → 几何-only(向后兼容,所有现有调用点零改动)。传了 → card 行附相应指令。 */
|
|
46
|
+
resolve?: (id: string) => CardDslContent | undefined): string;
|
|
47
|
+
/**
|
|
48
|
+
* 面向人的可读序列化(DSL 编辑器用)。
|
|
49
|
+
*
|
|
50
|
+
* v5 现状:与 strict {@link serializeCanvas} **逐字节收敛**(card 行带真实 @title/@content
|
|
51
|
+
* token,由 resolve 注入;旧的 ` # title:` 注释行已退役)。故本函数**委托** serializeCanvas,
|
|
52
|
+
* 不另留一份相同实现(消除漂移风险;F)。
|
|
53
|
+
*
|
|
54
|
+
* 保留独立命名的理由:它是编辑器的「人读视图」语义入口。将来若要加人读增强(如 @content
|
|
55
|
+
* 多行展开显示、注释),从**这里**分叉,strict 仍是机器往返形态。当前二者同形态。
|
|
56
|
+
*
|
|
57
|
+
* resolve: 可选,card id → {title, content}(消费者从 CardService 读)。不给 → 几何-only。
|
|
58
|
+
*/
|
|
59
|
+
export declare function serializeCanvasReadable(elements: CanvasElement[], resolve?: (id: string) => CardDslContent | undefined): string;
|
|
60
|
+
export declare function serializeElement(e: CanvasElement,
|
|
61
|
+
/** v5/v8:card 的内容与结构化字段(可选,由 serializeCanvas 的 resolve 注入)。非 card 元素忽略。 */
|
|
62
|
+
content?: CardDslContent): string;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { DSL_KINDS, DSL_MAX_TEXT_LEN, DSL_MAX_CONTENT_LEN, DSL_MAX_HREF_TARGETS, DSL_MAX_TAG_COUNT, DSL_MAX_LINK_COUNT, DSL_MAX_CODE_BLOCKS, DSL_MAX_QUOTES } from './dsl-grammar.js';
|
|
2
|
+
/**
|
|
3
|
+
* Serialize the canvas's active elements to a text block the AI can read.
|
|
4
|
+
* Pure function of the element list — no side-effects, no engine access.
|
|
5
|
+
*/
|
|
6
|
+
export function serializeCanvas(elements,
|
|
7
|
+
/** v5:可选,card id → 卡片内容(v8:含结构化字段,见 {@link CardDslContent})。
|
|
8
|
+
* 不传 → 几何-only(向后兼容,所有现有调用点零改动)。传了 → card 行附相应指令。 */
|
|
9
|
+
resolve) {
|
|
10
|
+
return elements
|
|
11
|
+
.filter((e) => DSL_KINDS.includes(e.kind))
|
|
12
|
+
.map((e) => serializeElement(e, e.kind === 'card' ? resolve?.(e.id) : undefined))
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.join('\n');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 面向人的可读序列化(DSL 编辑器用)。
|
|
18
|
+
*
|
|
19
|
+
* v5 现状:与 strict {@link serializeCanvas} **逐字节收敛**(card 行带真实 @title/@content
|
|
20
|
+
* token,由 resolve 注入;旧的 ` # title:` 注释行已退役)。故本函数**委托** serializeCanvas,
|
|
21
|
+
* 不另留一份相同实现(消除漂移风险;F)。
|
|
22
|
+
*
|
|
23
|
+
* 保留独立命名的理由:它是编辑器的「人读视图」语义入口。将来若要加人读增强(如 @content
|
|
24
|
+
* 多行展开显示、注释),从**这里**分叉,strict 仍是机器往返形态。当前二者同形态。
|
|
25
|
+
*
|
|
26
|
+
* resolve: 可选,card id → {title, content}(消费者从 CardService 读)。不给 → 几何-only。
|
|
27
|
+
*/
|
|
28
|
+
export function serializeCanvasReadable(elements, resolve) {
|
|
29
|
+
return serializeCanvas(elements, resolve);
|
|
30
|
+
}
|
|
31
|
+
export function serializeElement(e,
|
|
32
|
+
/** v5/v8:card 的内容与结构化字段(可选,由 serializeCanvas 的 resolve 注入)。非 card 元素忽略。 */
|
|
33
|
+
content) {
|
|
34
|
+
const pos = `@pos(${e.x.toFixed(1)},${e.y.toFixed(1)})`;
|
|
35
|
+
const color = e.color ? ` @color(${e.color})` : '';
|
|
36
|
+
switch (e.kind) {
|
|
37
|
+
case 'card': {
|
|
38
|
+
// v5:可选 @title/@content;v8:可选 @type/@tags/@links/@code/@quote(消费者注入)。
|
|
39
|
+
// 缺省几何-only(round-trip 与 v4 等价)。
|
|
40
|
+
// 超长防护(2026-08-29 P1):parser 对 @title/@content 静默截断(DoS 防护,保留)。
|
|
41
|
+
// serialize 侧若照常 emit 超长值,DSL 编辑器 Apply 会把**截断后的值**写回
|
|
42
|
+
// Card.body → 数据尾部永久丢失。故超长字段此处**不 emit**(parse 后为
|
|
43
|
+
// undefined,所有写回路径的 `!== undefined` 守卫自然跳过)——数据保住,
|
|
44
|
+
// 截断防护不动。text/frame text 同理走 textAttr helper。
|
|
45
|
+
const titleAttr = quotedAttr('title', content?.title, DSL_MAX_TEXT_LEN);
|
|
46
|
+
const contentAttr = quotedAttr('content', content?.content, DSL_MAX_CONTENT_LEN);
|
|
47
|
+
return `[card #${e.id}] ${pos} @size(${e.w.toFixed(1)},${e.h.toFixed(1)})${color}${titleAttr}${contentAttr}${metaGroup(e)}${metaHref(e)}${metaType(content)}${metaTags(content)}${metaLinks(content)}${metaCode(content)}${metaQuote(content)}`;
|
|
48
|
+
}
|
|
49
|
+
case 'rect':
|
|
50
|
+
return `[rect #${e.id}] ${pos} @size(${e.w.toFixed(1)},${e.h.toFixed(1)})${color}${metaGroup(e)}`;
|
|
51
|
+
case 'frame':
|
|
52
|
+
return (`[frame #${e.id}] ${pos} @size(${e.w.toFixed(1)},${e.h.toFixed(1)})` +
|
|
53
|
+
quotedAttr('text', e.text ?? '', DSL_MAX_TEXT_LEN) +
|
|
54
|
+
color +
|
|
55
|
+
metaGroup(e));
|
|
56
|
+
case 'text':
|
|
57
|
+
return (`[text #${e.id}] ${pos}${quotedAttr('text', e.text ?? '', DSL_MAX_TEXT_LEN)}${color}${metaGroup(e)}${metaCompute(e)}`);
|
|
58
|
+
case 'arrow': {
|
|
59
|
+
// Shared relation signature (label/color/dash/arrowhead/route).
|
|
60
|
+
// route 只在非 straight(或显式设过)时输出,保向后兼容(旧直线箭头无 @route)。
|
|
61
|
+
// route + 对应数据(curve / elbow)按 route 输出,三者同源见 arrowRoute。
|
|
62
|
+
const routeAttr = e.route === 'curve' || e.route === 'elbow' || e.route === 'straight'
|
|
63
|
+
? ` @route(${e.route})`
|
|
64
|
+
: '';
|
|
65
|
+
const elbowAttr = e.elbow && e.elbow.length > 0
|
|
66
|
+
? ` @elbow(${e.elbow.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(';')})`
|
|
67
|
+
: '';
|
|
68
|
+
const sig = quotedAttr('label', e.text ?? undefined, DSL_MAX_TEXT_LEN) +
|
|
69
|
+
color +
|
|
70
|
+
(e.dash ? ` @dash(${e.dash})` : '') +
|
|
71
|
+
(e.arrowhead ? ` @arrowhead(${e.arrowhead})` : '') +
|
|
72
|
+
(e.curve ? ` @curve(${e.curve.cx.toFixed(1)},${e.curve.cy.toFixed(1)})` : '') +
|
|
73
|
+
routeAttr +
|
|
74
|
+
elbowAttr +
|
|
75
|
+
(e.meta?.wikilink === true ? ' @wikilink' : '');
|
|
76
|
+
if (e.from && e.to) {
|
|
77
|
+
// Relation arrow: endpoint references.
|
|
78
|
+
return `[arrow #${e.id}] from #${e.from} to #${e.to}${sig}`;
|
|
79
|
+
}
|
|
80
|
+
// Free arrow: bbox encodes the segment (w/h may be negative for direction).
|
|
81
|
+
return `[arrow #${e.id}] ${pos} @size(${e.w.toFixed(1)},${e.h.toFixed(1)})${sig}`;
|
|
82
|
+
}
|
|
83
|
+
case 'freedraw':
|
|
84
|
+
// Position only — never the point sequence (R2 + privacy)。注意:freedraw 已出 DSL 契约
|
|
85
|
+
// (serializeCanvas 按 DSL_KINDS 过滤,不 emit)。本 case 仅供**直接调用方**(如 AI snapshot,
|
|
86
|
+
// 它是单向上下文格式、非往返 DSL)用;parseDsl 不接受 `[freedraw]`(→ unrecognized)。
|
|
87
|
+
return `[freedraw #${e.id}] ${pos}`;
|
|
88
|
+
default:
|
|
89
|
+
// ellipse / line / note / image (legacy) — not in the DSL。
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Escape a string for a quoted DSL value:\\ = backslash, \" = quote, \n = newline(v5,@content 多行),
|
|
94
|
+
* \` = backtick(v8,@code/@quote 内容里的 ``` 不提前闭合 AI prompt 的 markdown 围栏)。
|
|
95
|
+
* 顺序:先 \ (防后续插入的 \ 被二次转义),再 ",再换行,再反引号。是 dsl-parser unescapeQuoted 的逆
|
|
96
|
+
* (unescapeQuoted 的 \X→X 通用,天然对称)。 */
|
|
97
|
+
function escapeQuoted(s) {
|
|
98
|
+
return s
|
|
99
|
+
.replace(/\\/g, '\\\\')
|
|
100
|
+
.replace(/"/g, '\\"')
|
|
101
|
+
.replace(/\n/g, '\\n')
|
|
102
|
+
.replace(/`/g, '\\`');
|
|
103
|
+
}
|
|
104
|
+
/** 带长度上限的引号属性 emit(2026-08-29 P1):值为空/undefined → '';超上限 → ''
|
|
105
|
+
* (**不 emit**,防 parser 静默截断后经 Apply 写回毁数据,见 serializeElement card case 注释);
|
|
106
|
+
* 正常值 → ` @name("escaped")`。 */
|
|
107
|
+
function quotedAttr(name, value, max) {
|
|
108
|
+
if (typeof value !== 'string' || value === '' || value.length > max)
|
|
109
|
+
return '';
|
|
110
|
+
return ` @${name}("${escapeQuoted(value)}")`;
|
|
111
|
+
}
|
|
112
|
+
/** v7:读 element.meta.group(语义分组名)。非字符串 / 空 → 不 emit。
|
|
113
|
+
* 超长(>DSL_MAX_TEXT_LEN)不 emit:parse 侧 truncate 会截断(见 quotedAttr 注释)。 */
|
|
114
|
+
function metaGroup(e) {
|
|
115
|
+
const g = e.meta?.group;
|
|
116
|
+
return quotedAttr('group', typeof g === 'string' ? g : undefined, DSL_MAX_TEXT_LEN);
|
|
117
|
+
}
|
|
118
|
+
/** v7:读 element.meta.href(卡片显式语义引用目标 id 列表)。非数组 / 空 → 不 emit。
|
|
119
|
+
* emit 形如 ` @href(#a;#b)`(每个 id 补 `#`,`;` 分隔),是 parseHref 的逆。
|
|
120
|
+
* 超上限(>DSL_MAX_HREF_TARGETS)截到上限——parse 侧同样截断,**两侧同界**,
|
|
121
|
+
* round-trip 稳定(与文本字段不同:截尾丢的是列表项,parse 后仍可写回稳定子集,
|
|
122
|
+
* 不会造成「serialize 全量 → parse 截断 → 写回」的不对称丢失)。 */
|
|
123
|
+
function metaHref(e) {
|
|
124
|
+
const h = e.meta?.href;
|
|
125
|
+
if (!Array.isArray(h))
|
|
126
|
+
return '';
|
|
127
|
+
const ids = h
|
|
128
|
+
.filter((x) => typeof x === 'string' && x !== '')
|
|
129
|
+
.slice(0, DSL_MAX_HREF_TARGETS);
|
|
130
|
+
if (ids.length === 0)
|
|
131
|
+
return '';
|
|
132
|
+
return ` @href(${ids.map((id) => '#' + id).join(';')})`;
|
|
133
|
+
}
|
|
134
|
+
/** v7:读 element.meta.compute(text 元素安全公式原文)。非字符串 / 空 → 不 emit。
|
|
135
|
+
* 超长不 emit(同 metaGroup 理由)。 */
|
|
136
|
+
function metaCompute(e) {
|
|
137
|
+
const c = e.meta?.compute;
|
|
138
|
+
return quotedAttr('compute', typeof c === 'string' ? c : undefined, DSL_MAX_TEXT_LEN);
|
|
139
|
+
}
|
|
140
|
+
/** v8:读注入的卡片 type(语义类型)。缺省 / 空 → 不 emit。CardType 枚举值(note|image|link|code|quote)
|
|
141
|
+
* 为安全 token,无需转义。image 卡照样 emit type(media 另排除,不进 DSL)。 */
|
|
142
|
+
function metaType(c) {
|
|
143
|
+
return c?.type ? ` @type(${c.type})` : '';
|
|
144
|
+
}
|
|
145
|
+
/** v8 list 值编码:encodeURIComponent 之外再编码 `(` `)`——grammar 用 `[^)]+` 捕获 @tags/@links
|
|
146
|
+
* (dsl.peggy tagsDir/linksDir),裸 `)` 会提前闭合指令并**级联吞掉后续项**(如 Wikipedia
|
|
147
|
+
* `Cat_(disambiguation)` URL 丢尾 `)`→ 404,其后所有项被当残留丢弃)。parse 侧
|
|
148
|
+
* decodeURIComponent(dsl-parser.ts parseTagList/parseLinkList)自动反向,无需改 grammar。 */
|
|
149
|
+
function encodeListValue(v) {
|
|
150
|
+
try {
|
|
151
|
+
return encodeURIComponent(v).replace(/\(/g, '%28').replace(/\)/g, '%29');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// 孤立代理对(非法 Unicode,来自异常切片而非正常输入)会让 encodeURIComponent
|
|
155
|
+
// 抛 URIError,进而**整张画布**导出 / AI 快照崩。降级:替换孤立代理为 U+FFFD
|
|
156
|
+
// 再编码,保导出可用(非法输入本就不要求逐字节往返)。
|
|
157
|
+
return encodeURIComponent(v.replace(/[\ud800-\udfff]/g, '�')).replace(/\(/g, '%28').replace(/\)/g, '%29');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** v8:读注入的卡片 tags。value 过滤空 → 各 encodeListValue(防 `;` 分隔符碰撞 + `)` 提前闭合)→ `;` 连接。
|
|
161
|
+
* 无有效 tag → 不 emit。是 parseTagList 的逆。
|
|
162
|
+
* 超数防护(2026-08-29 P1):截到 DSL_MAX_TAG_COUNT —— 与 parse 侧同界,round-trip 稳定。 */
|
|
163
|
+
function metaTags(c) {
|
|
164
|
+
const values = (c?.tags ?? [])
|
|
165
|
+
.map((t) => (typeof t?.value === 'string' ? t.value.trim() : ''))
|
|
166
|
+
.filter((v) => v !== '')
|
|
167
|
+
.slice(0, DSL_MAX_TAG_COUNT);
|
|
168
|
+
return values.length > 0 ? ` @tags(${values.map(encodeListValue).join(';')})` : '';
|
|
169
|
+
}
|
|
170
|
+
/** v8:读注入的卡片 links(仅 URL,见 CardDslContent 说明)。url 过滤空 → 各 encodeListValue
|
|
171
|
+
* (URL 含 `;`/`=`/`&` 会撞分隔符;含 `)` 如 Wikipedia 消歧义页会提前闭合)→ `;` 连接。
|
|
172
|
+
* 无有效 url → 不 emit。是 parseLinkList 的逆。超数防护同 metaTags(DSL_MAX_LINK_COUNT)。 */
|
|
173
|
+
function metaLinks(c) {
|
|
174
|
+
const urls = (c?.links ?? [])
|
|
175
|
+
.map((l) => (typeof l?.url === 'string' ? l.url.trim() : ''))
|
|
176
|
+
.filter((u) => u !== '')
|
|
177
|
+
.slice(0, DSL_MAX_LINK_COUNT);
|
|
178
|
+
return urls.length > 0 ? ` @links(${urls.map(encodeListValue).join(';')})` : '';
|
|
179
|
+
}
|
|
180
|
+
/** v8:读注入的卡片 codeSnippets。每个 emit 一条 ` @code(lang,"code"[,"caption"])`(可重复指令)。
|
|
181
|
+
* lang 收窄到 grammar codeLang 字符集(越界字符丢弃,防破坏指令;空语言合法)。code/caption 走
|
|
182
|
+
* escapeQuoted(多行/引号/反引号)。是 fold code 累积的逆。
|
|
183
|
+
* 超长防护(2026-08-29 P1):code 超 DSL_MAX_CONTENT_LEN / 块数超 DSL_MAX_CODE_BLOCKS
|
|
184
|
+
* 的条目不 emit —— parse 侧会截断/截数,照常 emit 会经 Apply 写回截断值(同 quotedAttr 理由)。 */
|
|
185
|
+
function metaCode(c) {
|
|
186
|
+
return (c?.codeSnippets ?? [])
|
|
187
|
+
.filter((b) => typeof b?.code === 'string' && b.code !== '' && b.code.length <= DSL_MAX_CONTENT_LEN)
|
|
188
|
+
.slice(0, DSL_MAX_CODE_BLOCKS)
|
|
189
|
+
.map((b) => {
|
|
190
|
+
const lang = (typeof b.language === 'string' ? b.language : '').replace(/[^a-zA-Z0-9_+#.-]/g, '');
|
|
191
|
+
const caption = typeof b.caption === 'string' && b.caption !== '' ? `,"${escapeQuoted(b.caption)}"` : '';
|
|
192
|
+
return ` @code(${lang},"${escapeQuoted(b.code)}"${caption})`;
|
|
193
|
+
})
|
|
194
|
+
.join('');
|
|
195
|
+
}
|
|
196
|
+
/** v8:读注入的卡片 quotes。每个 emit 一条 ` @quote("text"[,"attribution"[,"sourceUrl"]])`(可重复)。
|
|
197
|
+
* arity:有 sourceUrl → 三参(attribution 缺省补空串占位);否则有 attribution → 两参;否则一参。
|
|
198
|
+
* 各值走 escapeQuoted。是 fold quote 累积的逆(空串占位 parse 侧归一 undefined)。
|
|
199
|
+
* 超长/超数防护同 metaCode(2026-08-29 P1)。 */
|
|
200
|
+
function metaQuote(c) {
|
|
201
|
+
return (c?.quotes ?? [])
|
|
202
|
+
.filter((q) => typeof q?.text === 'string' && q.text !== '' && q.text.length <= DSL_MAX_CONTENT_LEN)
|
|
203
|
+
.slice(0, DSL_MAX_QUOTES)
|
|
204
|
+
.map((q) => {
|
|
205
|
+
const text = `"${escapeQuoted(q.text)}"`;
|
|
206
|
+
const by = typeof q.attribution === 'string' ? q.attribution : '';
|
|
207
|
+
const url = typeof q.sourceUrl === 'string' ? q.sourceUrl : '';
|
|
208
|
+
if (url !== '')
|
|
209
|
+
return ` @quote(${text},"${escapeQuoted(by)}","${escapeQuoted(url)}")`;
|
|
210
|
+
if (by !== '')
|
|
211
|
+
return ` @quote(${text},"${escapeQuoted(by)}")`;
|
|
212
|
+
return ` @quote(${text})`;
|
|
213
|
+
})
|
|
214
|
+
.join('');
|
|
215
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** CLI 结果:stdout(报告/JSON)与 stderr(警告)分流,code 即进程退出码。 */
|
|
3
|
+
export type CliResult = {
|
|
4
|
+
code: 0 | 1;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function validateText(text: string): CliResult;
|
|
9
|
+
export declare function renderText(text: string, opts?: {
|
|
10
|
+
svg?: boolean;
|
|
11
|
+
}): CliResult;
|
|
12
|
+
export declare function roundtripText(text: string): CliResult;
|
|
13
|
+
export declare function main(argv: readonly string[]): number;
|