@open-feishu-card/validator 0.1.5 → 0.1.7

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/dist/index.cjs CHANGED
@@ -26,64 +26,155 @@ function pointerToString(path) {
26
26
  return "/" + path.map((s) => String(s).replace(/~/g, "~0").replace(/\//g, "~1")).join("/");
27
27
  }
28
28
  var DEPRECATED_PROPS = {
29
- // 常见 1.0 → 2.0 迁移期废弃写法
30
- i18n_elements: "2.0 \u8BF7\u4F7F\u7528 body.elements + config.locale",
31
- card_link: "2.0 \u8BF7\u4F7F\u7528 elements \u4E2D\u7684 button/open_url \u884C\u4E3A"
29
+ i18n_elements: "2.0 \u4E0D\u518D\u652F\u6301\u5168\u5C40 i18n_elements\uFF0C\u8BF7\u4F7F\u7528\u7EC4\u4EF6\u7EA7 i18n \u5B57\u6BB5",
30
+ i18n_header: "2.0 \u4E0D\u518D\u652F\u6301 i18n_header\uFF0C\u8BF7\u4F7F\u7528 header \u5185\u7684\u5C40\u90E8\u591A\u8BED\u8A00\u5B57\u6BB5"
32
31
  };
33
- function validateAst(result, options = {}) {
34
- const diagnostics = [];
35
- for (const e of result.errors) {
32
+ var BUILDER_ROOT_KEYS = /* @__PURE__ */ new Set(["name", "dsl", "variables"]);
33
+ function getProp(node, key) {
34
+ return node.properties.find((p) => p.key.value === key)?.value;
35
+ }
36
+ function validateBuilderEnvelope(resolved, diagnostics) {
37
+ if (resolved.kind !== "builder") return;
38
+ const root = resolved.rootAst;
39
+ for (const prop of root.properties) {
40
+ const key = prop.key.value;
41
+ if (!BUILDER_ROOT_KEYS.has(key)) {
42
+ diagnostics.push({
43
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
44
+ severity: "warning",
45
+ range: prop.key.range,
46
+ path: "/" + key,
47
+ code: "builder-unknown-key"
48
+ });
49
+ }
50
+ }
51
+ const nameNode = getProp(root, "name");
52
+ if (nameNode && nameNode.type !== "string") {
36
53
  diagnostics.push({
37
- message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
54
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
38
55
  severity: "error",
39
- range: e.range,
40
- path: "/"
56
+ range: nameNode.range,
57
+ path: "/name",
58
+ code: "builder-envelope"
41
59
  });
42
60
  }
43
- if (!result.ast || result.data === void 0) return diagnostics;
44
- const checkResult = schema.FeishuCardV2Schema.safeParse(result.data);
61
+ const variablesNode = getProp(root, "variables");
62
+ if (variablesNode && variablesNode.type !== "array") {
63
+ diagnostics.push({
64
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
65
+ severity: "error",
66
+ range: variablesNode.range,
67
+ path: "/variables",
68
+ code: "builder-envelope"
69
+ });
70
+ }
71
+ }
72
+ function validateCardAgainstSchema(cardAst, cardData, pathPrefix, options, diagnostics) {
73
+ const checkResult = schema.FeishuCardV2Schema.safeParse(cardData);
45
74
  if (!checkResult.success) {
46
75
  for (const issue of checkResult.error.issues) {
47
- const node = findNodeAtPointer(result.ast, issue.path);
48
- const range = node ? node.range : { start: result.ast.range.start, end: result.ast.range.start };
76
+ const node = findNodeAtPointer(cardAst, issue.path);
77
+ const range = node ? node.range : { start: cardAst.range.start, end: cardAst.range.start };
49
78
  diagnostics.push({
50
79
  message: issue.message,
51
80
  severity: "error",
52
81
  range,
53
- path: pointerToString(issue.path)
82
+ path: parser.joinCardPointer(pathPrefix, pointerToString(issue.path))
54
83
  });
55
84
  }
56
85
  }
57
86
  const deprecated = { ...DEPRECATED_PROPS, ...options.deprecatedProps ?? {} };
58
- for (const prop of result.ast.properties) {
87
+ for (const prop of cardAst.properties) {
59
88
  const hint = deprecated[prop.key.value];
60
89
  if (hint) {
61
90
  diagnostics.push({
62
91
  message: `\u5E9F\u5F03\u5C5E\u6027 "${prop.key.value}": ${hint}`,
63
92
  severity: "warning",
64
93
  range: prop.key.range,
65
- path: "/" + prop.key.value,
94
+ path: parser.joinCardPointer(pathPrefix, "/" + prop.key.value),
66
95
  code: "deprecated-prop"
67
96
  });
68
97
  }
69
98
  }
99
+ }
100
+ function validateAst(result, options = {}) {
101
+ const diagnostics = [];
102
+ for (const e of result.errors) {
103
+ diagnostics.push({
104
+ message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
105
+ severity: "error",
106
+ range: e.range,
107
+ path: "/"
108
+ });
109
+ }
110
+ if (!result.ast || result.data === void 0) return diagnostics;
111
+ const resolved = parser.resolveCardDocument(result);
112
+ if (!resolved) return diagnostics;
113
+ validateBuilderEnvelope(resolved, diagnostics);
114
+ validateCardAgainstSchema(
115
+ resolved.cardAst,
116
+ resolved.cardData,
117
+ resolved.pathPrefix,
118
+ options,
119
+ diagnostics
120
+ );
70
121
  return diagnostics;
71
122
  }
72
123
  function validate(source, options) {
73
124
  return validateAst(parser.parse(source), options);
74
125
  }
75
126
  function validateData(data) {
76
- const checkResult = schema.FeishuCardV2Schema.safeParse(data);
77
- if (checkResult.success) return [];
78
- return checkResult.error.issues.map((issue) => ({
79
- message: issue.message,
80
- severity: "error",
81
- range: {
82
- start: { offset: 0, line: 1, column: 1 },
83
- end: { offset: 0, line: 1, column: 1 }
84
- },
85
- path: pointerToString(issue.path)
86
- }));
127
+ const ZERO = {
128
+ start: { offset: 0, line: 1, column: 1 },
129
+ end: { offset: 0, line: 1, column: 1 }
130
+ };
131
+ const diagnostics = [];
132
+ if (parser.isBuilderDocument(data)) {
133
+ const rec = data;
134
+ for (const key of Object.keys(rec)) {
135
+ if (!BUILDER_ROOT_KEYS.has(key)) {
136
+ diagnostics.push({
137
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
138
+ severity: "warning",
139
+ range: ZERO,
140
+ path: "/" + key,
141
+ code: "builder-unknown-key"
142
+ });
143
+ }
144
+ }
145
+ if ("name" in rec && typeof rec.name !== "string") {
146
+ diagnostics.push({
147
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
148
+ severity: "error",
149
+ range: ZERO,
150
+ path: "/name",
151
+ code: "builder-envelope"
152
+ });
153
+ }
154
+ if ("variables" in rec && !Array.isArray(rec.variables)) {
155
+ diagnostics.push({
156
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
157
+ severity: "error",
158
+ range: ZERO,
159
+ path: "/variables",
160
+ code: "builder-envelope"
161
+ });
162
+ }
163
+ }
164
+ const card = parser.unwrapCardData(data);
165
+ const checkResult = schema.FeishuCardV2Schema.safeParse(card);
166
+ if (!checkResult.success) {
167
+ const prefix = parser.isBuilderDocument(data) ? "/dsl" : "";
168
+ for (const issue of checkResult.error.issues) {
169
+ diagnostics.push({
170
+ message: issue.message,
171
+ severity: "error",
172
+ range: ZERO,
173
+ path: parser.joinCardPointer(prefix, pointerToString(issue.path))
174
+ });
175
+ }
176
+ }
177
+ return diagnostics;
87
178
  }
88
179
 
89
180
  exports.findNodeAtPointer = findNodeAtPointer;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * 把 Zod Schema 2.0 的校验结果映射回 parser 产出的 AST 节点,
5
5
  * 让每条诊断都精确定位到源码的 Line/Column/Range(字符级)。
6
+ *
7
+ * 输入可为裸 Card JSON,或搭建工具文档 `{ name, dsl, variables }`;
8
+ * Schema 只校验解包后的卡片层。
6
9
  */
7
10
  import { type JsonNode, type JsonObjectNode, type ParseResult, type Range } from '@open-feishu-card/parser';
8
11
  export type DiagnosticSeverity = 'error' | 'warning' | 'information' | 'hint';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,KAAK,EACX,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;AAE9E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,QAAQ,GAAG,SAAS,CAgBnG;AAaD,MAAM,WAAW,eAAe;IAC9B,yBAAyB;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAED,6BAA6B;AAC7B,wBAAgB,WAAW,CACzB,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,eAAoB,GAC5B,UAAU,EAAE,CA4Cd;AAED,wBAAwB;AACxB,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,UAAU,EAAE,CAEhF;AAED,yCAAyC;AACzC,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,CAYxD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,KAAK,EAEX,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;AAE9E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,QAAQ,GAAG,SAAS,CAgBnG;AAcD,MAAM,WAAW,eAAe;IAC9B,yBAAyB;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC1C;AAsFD,6BAA6B;AAC7B,wBAAgB,WAAW,CACzB,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,eAAoB,GAC5B,UAAU,EAAE,CAyBd;AAED,wBAAwB;AACxB,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,UAAU,EAAE,CAEhF;AAED,yCAAyC;AACzC,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,CAsDxD"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { parse } from '@open-feishu-card/parser';
1
+ import { resolveCardDocument, parse, isBuilderDocument, unwrapCardData, joinCardPointer } from '@open-feishu-card/parser';
2
2
  import { FeishuCardV2Schema } from '@open-feishu-card/schema';
3
3
 
4
4
  // src/index.ts
@@ -24,64 +24,155 @@ function pointerToString(path) {
24
24
  return "/" + path.map((s) => String(s).replace(/~/g, "~0").replace(/\//g, "~1")).join("/");
25
25
  }
26
26
  var DEPRECATED_PROPS = {
27
- // 常见 1.0 → 2.0 迁移期废弃写法
28
- i18n_elements: "2.0 \u8BF7\u4F7F\u7528 body.elements + config.locale",
29
- card_link: "2.0 \u8BF7\u4F7F\u7528 elements \u4E2D\u7684 button/open_url \u884C\u4E3A"
27
+ i18n_elements: "2.0 \u4E0D\u518D\u652F\u6301\u5168\u5C40 i18n_elements\uFF0C\u8BF7\u4F7F\u7528\u7EC4\u4EF6\u7EA7 i18n \u5B57\u6BB5",
28
+ i18n_header: "2.0 \u4E0D\u518D\u652F\u6301 i18n_header\uFF0C\u8BF7\u4F7F\u7528 header \u5185\u7684\u5C40\u90E8\u591A\u8BED\u8A00\u5B57\u6BB5"
30
29
  };
31
- function validateAst(result, options = {}) {
32
- const diagnostics = [];
33
- for (const e of result.errors) {
30
+ var BUILDER_ROOT_KEYS = /* @__PURE__ */ new Set(["name", "dsl", "variables"]);
31
+ function getProp(node, key) {
32
+ return node.properties.find((p) => p.key.value === key)?.value;
33
+ }
34
+ function validateBuilderEnvelope(resolved, diagnostics) {
35
+ if (resolved.kind !== "builder") return;
36
+ const root = resolved.rootAst;
37
+ for (const prop of root.properties) {
38
+ const key = prop.key.value;
39
+ if (!BUILDER_ROOT_KEYS.has(key)) {
40
+ diagnostics.push({
41
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
42
+ severity: "warning",
43
+ range: prop.key.range,
44
+ path: "/" + key,
45
+ code: "builder-unknown-key"
46
+ });
47
+ }
48
+ }
49
+ const nameNode = getProp(root, "name");
50
+ if (nameNode && nameNode.type !== "string") {
34
51
  diagnostics.push({
35
- message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
52
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
36
53
  severity: "error",
37
- range: e.range,
38
- path: "/"
54
+ range: nameNode.range,
55
+ path: "/name",
56
+ code: "builder-envelope"
39
57
  });
40
58
  }
41
- if (!result.ast || result.data === void 0) return diagnostics;
42
- const checkResult = FeishuCardV2Schema.safeParse(result.data);
59
+ const variablesNode = getProp(root, "variables");
60
+ if (variablesNode && variablesNode.type !== "array") {
61
+ diagnostics.push({
62
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
63
+ severity: "error",
64
+ range: variablesNode.range,
65
+ path: "/variables",
66
+ code: "builder-envelope"
67
+ });
68
+ }
69
+ }
70
+ function validateCardAgainstSchema(cardAst, cardData, pathPrefix, options, diagnostics) {
71
+ const checkResult = FeishuCardV2Schema.safeParse(cardData);
43
72
  if (!checkResult.success) {
44
73
  for (const issue of checkResult.error.issues) {
45
- const node = findNodeAtPointer(result.ast, issue.path);
46
- const range = node ? node.range : { start: result.ast.range.start, end: result.ast.range.start };
74
+ const node = findNodeAtPointer(cardAst, issue.path);
75
+ const range = node ? node.range : { start: cardAst.range.start, end: cardAst.range.start };
47
76
  diagnostics.push({
48
77
  message: issue.message,
49
78
  severity: "error",
50
79
  range,
51
- path: pointerToString(issue.path)
80
+ path: joinCardPointer(pathPrefix, pointerToString(issue.path))
52
81
  });
53
82
  }
54
83
  }
55
84
  const deprecated = { ...DEPRECATED_PROPS, ...options.deprecatedProps ?? {} };
56
- for (const prop of result.ast.properties) {
85
+ for (const prop of cardAst.properties) {
57
86
  const hint = deprecated[prop.key.value];
58
87
  if (hint) {
59
88
  diagnostics.push({
60
89
  message: `\u5E9F\u5F03\u5C5E\u6027 "${prop.key.value}": ${hint}`,
61
90
  severity: "warning",
62
91
  range: prop.key.range,
63
- path: "/" + prop.key.value,
92
+ path: joinCardPointer(pathPrefix, "/" + prop.key.value),
64
93
  code: "deprecated-prop"
65
94
  });
66
95
  }
67
96
  }
97
+ }
98
+ function validateAst(result, options = {}) {
99
+ const diagnostics = [];
100
+ for (const e of result.errors) {
101
+ diagnostics.push({
102
+ message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
103
+ severity: "error",
104
+ range: e.range,
105
+ path: "/"
106
+ });
107
+ }
108
+ if (!result.ast || result.data === void 0) return diagnostics;
109
+ const resolved = resolveCardDocument(result);
110
+ if (!resolved) return diagnostics;
111
+ validateBuilderEnvelope(resolved, diagnostics);
112
+ validateCardAgainstSchema(
113
+ resolved.cardAst,
114
+ resolved.cardData,
115
+ resolved.pathPrefix,
116
+ options,
117
+ diagnostics
118
+ );
68
119
  return diagnostics;
69
120
  }
70
121
  function validate(source, options) {
71
122
  return validateAst(parse(source), options);
72
123
  }
73
124
  function validateData(data) {
74
- const checkResult = FeishuCardV2Schema.safeParse(data);
75
- if (checkResult.success) return [];
76
- return checkResult.error.issues.map((issue) => ({
77
- message: issue.message,
78
- severity: "error",
79
- range: {
80
- start: { offset: 0, line: 1, column: 1 },
81
- end: { offset: 0, line: 1, column: 1 }
82
- },
83
- path: pointerToString(issue.path)
84
- }));
125
+ const ZERO = {
126
+ start: { offset: 0, line: 1, column: 1 },
127
+ end: { offset: 0, line: 1, column: 1 }
128
+ };
129
+ const diagnostics = [];
130
+ if (isBuilderDocument(data)) {
131
+ const rec = data;
132
+ for (const key of Object.keys(rec)) {
133
+ if (!BUILDER_ROOT_KEYS.has(key)) {
134
+ diagnostics.push({
135
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
136
+ severity: "warning",
137
+ range: ZERO,
138
+ path: "/" + key,
139
+ code: "builder-unknown-key"
140
+ });
141
+ }
142
+ }
143
+ if ("name" in rec && typeof rec.name !== "string") {
144
+ diagnostics.push({
145
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
146
+ severity: "error",
147
+ range: ZERO,
148
+ path: "/name",
149
+ code: "builder-envelope"
150
+ });
151
+ }
152
+ if ("variables" in rec && !Array.isArray(rec.variables)) {
153
+ diagnostics.push({
154
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
155
+ severity: "error",
156
+ range: ZERO,
157
+ path: "/variables",
158
+ code: "builder-envelope"
159
+ });
160
+ }
161
+ }
162
+ const card = unwrapCardData(data);
163
+ const checkResult = FeishuCardV2Schema.safeParse(card);
164
+ if (!checkResult.success) {
165
+ const prefix = isBuilderDocument(data) ? "/dsl" : "";
166
+ for (const issue of checkResult.error.issues) {
167
+ diagnostics.push({
168
+ message: issue.message,
169
+ severity: "error",
170
+ range: ZERO,
171
+ path: joinCardPointer(prefix, pointerToString(issue.path))
172
+ });
173
+ }
174
+ }
175
+ return diagnostics;
85
176
  }
86
177
 
87
178
  export { findNodeAtPointer, validate, validateAst, validateData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/validator",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "对比 AST 与 Schema 2.0,输出精确定位到源码字符级的诊断信息",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -17,8 +17,8 @@
17
17
  "dist"
18
18
  ],
19
19
  "dependencies": {
20
- "@open-feishu-card/parser": "0.1.2",
21
- "@open-feishu-card/schema": "0.1.5"
20
+ "@open-feishu-card/parser": "0.1.3",
21
+ "@open-feishu-card/schema": "0.1.6"
22
22
  },
23
23
  "devDependencies": {
24
24
  "tsup": "^8.3.5",