@open-feishu-card/validator 0.1.5 → 0.1.6

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
@@ -30,60 +30,152 @@ var DEPRECATED_PROPS = {
30
30
  i18n_elements: "2.0 \u8BF7\u4F7F\u7528 body.elements + config.locale",
31
31
  card_link: "2.0 \u8BF7\u4F7F\u7528 elements \u4E2D\u7684 button/open_url \u884C\u4E3A"
32
32
  };
33
- function validateAst(result, options = {}) {
34
- const diagnostics = [];
35
- for (const e of result.errors) {
33
+ var BUILDER_ROOT_KEYS = /* @__PURE__ */ new Set(["name", "dsl", "variables"]);
34
+ function getProp(node, key) {
35
+ return node.properties.find((p) => p.key.value === key)?.value;
36
+ }
37
+ function validateBuilderEnvelope(resolved, diagnostics) {
38
+ if (resolved.kind !== "builder") return;
39
+ const root = resolved.rootAst;
40
+ for (const prop of root.properties) {
41
+ const key = prop.key.value;
42
+ if (!BUILDER_ROOT_KEYS.has(key)) {
43
+ diagnostics.push({
44
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
45
+ severity: "warning",
46
+ range: prop.key.range,
47
+ path: "/" + key,
48
+ code: "builder-unknown-key"
49
+ });
50
+ }
51
+ }
52
+ const nameNode = getProp(root, "name");
53
+ if (nameNode && nameNode.type !== "string") {
36
54
  diagnostics.push({
37
- message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
55
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
38
56
  severity: "error",
39
- range: e.range,
40
- path: "/"
57
+ range: nameNode.range,
58
+ path: "/name",
59
+ code: "builder-envelope"
41
60
  });
42
61
  }
43
- if (!result.ast || result.data === void 0) return diagnostics;
44
- const checkResult = schema.FeishuCardV2Schema.safeParse(result.data);
62
+ const variablesNode = getProp(root, "variables");
63
+ if (variablesNode && variablesNode.type !== "array") {
64
+ diagnostics.push({
65
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
66
+ severity: "error",
67
+ range: variablesNode.range,
68
+ path: "/variables",
69
+ code: "builder-envelope"
70
+ });
71
+ }
72
+ }
73
+ function validateCardAgainstSchema(cardAst, cardData, pathPrefix, options, diagnostics) {
74
+ const checkResult = schema.FeishuCardV2Schema.safeParse(cardData);
45
75
  if (!checkResult.success) {
46
76
  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 };
77
+ const node = findNodeAtPointer(cardAst, issue.path);
78
+ const range = node ? node.range : { start: cardAst.range.start, end: cardAst.range.start };
49
79
  diagnostics.push({
50
80
  message: issue.message,
51
81
  severity: "error",
52
82
  range,
53
- path: pointerToString(issue.path)
83
+ path: parser.joinCardPointer(pathPrefix, pointerToString(issue.path))
54
84
  });
55
85
  }
56
86
  }
57
87
  const deprecated = { ...DEPRECATED_PROPS, ...options.deprecatedProps ?? {} };
58
- for (const prop of result.ast.properties) {
88
+ for (const prop of cardAst.properties) {
59
89
  const hint = deprecated[prop.key.value];
60
90
  if (hint) {
61
91
  diagnostics.push({
62
92
  message: `\u5E9F\u5F03\u5C5E\u6027 "${prop.key.value}": ${hint}`,
63
93
  severity: "warning",
64
94
  range: prop.key.range,
65
- path: "/" + prop.key.value,
95
+ path: parser.joinCardPointer(pathPrefix, "/" + prop.key.value),
66
96
  code: "deprecated-prop"
67
97
  });
68
98
  }
69
99
  }
100
+ }
101
+ function validateAst(result, options = {}) {
102
+ const diagnostics = [];
103
+ for (const e of result.errors) {
104
+ diagnostics.push({
105
+ message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
106
+ severity: "error",
107
+ range: e.range,
108
+ path: "/"
109
+ });
110
+ }
111
+ if (!result.ast || result.data === void 0) return diagnostics;
112
+ const resolved = parser.resolveCardDocument(result);
113
+ if (!resolved) return diagnostics;
114
+ validateBuilderEnvelope(resolved, diagnostics);
115
+ validateCardAgainstSchema(
116
+ resolved.cardAst,
117
+ resolved.cardData,
118
+ resolved.pathPrefix,
119
+ options,
120
+ diagnostics
121
+ );
70
122
  return diagnostics;
71
123
  }
72
124
  function validate(source, options) {
73
125
  return validateAst(parser.parse(source), options);
74
126
  }
75
127
  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
- }));
128
+ const ZERO = {
129
+ start: { offset: 0, line: 1, column: 1 },
130
+ end: { offset: 0, line: 1, column: 1 }
131
+ };
132
+ const diagnostics = [];
133
+ if (parser.isBuilderDocument(data)) {
134
+ const rec = data;
135
+ for (const key of Object.keys(rec)) {
136
+ if (!BUILDER_ROOT_KEYS.has(key)) {
137
+ diagnostics.push({
138
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
139
+ severity: "warning",
140
+ range: ZERO,
141
+ path: "/" + key,
142
+ code: "builder-unknown-key"
143
+ });
144
+ }
145
+ }
146
+ if ("name" in rec && typeof rec.name !== "string") {
147
+ diagnostics.push({
148
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
149
+ severity: "error",
150
+ range: ZERO,
151
+ path: "/name",
152
+ code: "builder-envelope"
153
+ });
154
+ }
155
+ if ("variables" in rec && !Array.isArray(rec.variables)) {
156
+ diagnostics.push({
157
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
158
+ severity: "error",
159
+ range: ZERO,
160
+ path: "/variables",
161
+ code: "builder-envelope"
162
+ });
163
+ }
164
+ }
165
+ const card = parser.unwrapCardData(data);
166
+ const checkResult = schema.FeishuCardV2Schema.safeParse(card);
167
+ if (!checkResult.success) {
168
+ const prefix = parser.isBuilderDocument(data) ? "/dsl" : "";
169
+ for (const issue of checkResult.error.issues) {
170
+ diagnostics.push({
171
+ message: issue.message,
172
+ severity: "error",
173
+ range: ZERO,
174
+ path: parser.joinCardPointer(prefix, pointerToString(issue.path))
175
+ });
176
+ }
177
+ }
178
+ return diagnostics;
87
179
  }
88
180
 
89
181
  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;AAeD,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
@@ -28,60 +28,152 @@ var DEPRECATED_PROPS = {
28
28
  i18n_elements: "2.0 \u8BF7\u4F7F\u7528 body.elements + config.locale",
29
29
  card_link: "2.0 \u8BF7\u4F7F\u7528 elements \u4E2D\u7684 button/open_url \u884C\u4E3A"
30
30
  };
31
- function validateAst(result, options = {}) {
32
- const diagnostics = [];
33
- for (const e of result.errors) {
31
+ var BUILDER_ROOT_KEYS = /* @__PURE__ */ new Set(["name", "dsl", "variables"]);
32
+ function getProp(node, key) {
33
+ return node.properties.find((p) => p.key.value === key)?.value;
34
+ }
35
+ function validateBuilderEnvelope(resolved, diagnostics) {
36
+ if (resolved.kind !== "builder") return;
37
+ const root = resolved.rootAst;
38
+ for (const prop of root.properties) {
39
+ const key = prop.key.value;
40
+ if (!BUILDER_ROOT_KEYS.has(key)) {
41
+ diagnostics.push({
42
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
43
+ severity: "warning",
44
+ range: prop.key.range,
45
+ path: "/" + key,
46
+ code: "builder-unknown-key"
47
+ });
48
+ }
49
+ }
50
+ const nameNode = getProp(root, "name");
51
+ if (nameNode && nameNode.type !== "string") {
34
52
  diagnostics.push({
35
- message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
53
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
36
54
  severity: "error",
37
- range: e.range,
38
- path: "/"
55
+ range: nameNode.range,
56
+ path: "/name",
57
+ code: "builder-envelope"
39
58
  });
40
59
  }
41
- if (!result.ast || result.data === void 0) return diagnostics;
42
- const checkResult = FeishuCardV2Schema.safeParse(result.data);
60
+ const variablesNode = getProp(root, "variables");
61
+ if (variablesNode && variablesNode.type !== "array") {
62
+ diagnostics.push({
63
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
64
+ severity: "error",
65
+ range: variablesNode.range,
66
+ path: "/variables",
67
+ code: "builder-envelope"
68
+ });
69
+ }
70
+ }
71
+ function validateCardAgainstSchema(cardAst, cardData, pathPrefix, options, diagnostics) {
72
+ const checkResult = FeishuCardV2Schema.safeParse(cardData);
43
73
  if (!checkResult.success) {
44
74
  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 };
75
+ const node = findNodeAtPointer(cardAst, issue.path);
76
+ const range = node ? node.range : { start: cardAst.range.start, end: cardAst.range.start };
47
77
  diagnostics.push({
48
78
  message: issue.message,
49
79
  severity: "error",
50
80
  range,
51
- path: pointerToString(issue.path)
81
+ path: joinCardPointer(pathPrefix, pointerToString(issue.path))
52
82
  });
53
83
  }
54
84
  }
55
85
  const deprecated = { ...DEPRECATED_PROPS, ...options.deprecatedProps ?? {} };
56
- for (const prop of result.ast.properties) {
86
+ for (const prop of cardAst.properties) {
57
87
  const hint = deprecated[prop.key.value];
58
88
  if (hint) {
59
89
  diagnostics.push({
60
90
  message: `\u5E9F\u5F03\u5C5E\u6027 "${prop.key.value}": ${hint}`,
61
91
  severity: "warning",
62
92
  range: prop.key.range,
63
- path: "/" + prop.key.value,
93
+ path: joinCardPointer(pathPrefix, "/" + prop.key.value),
64
94
  code: "deprecated-prop"
65
95
  });
66
96
  }
67
97
  }
98
+ }
99
+ function validateAst(result, options = {}) {
100
+ const diagnostics = [];
101
+ for (const e of result.errors) {
102
+ diagnostics.push({
103
+ message: `JSON \u8BED\u6CD5\u9519\u8BEF: ${e.message}`,
104
+ severity: "error",
105
+ range: e.range,
106
+ path: "/"
107
+ });
108
+ }
109
+ if (!result.ast || result.data === void 0) return diagnostics;
110
+ const resolved = resolveCardDocument(result);
111
+ if (!resolved) return diagnostics;
112
+ validateBuilderEnvelope(resolved, diagnostics);
113
+ validateCardAgainstSchema(
114
+ resolved.cardAst,
115
+ resolved.cardData,
116
+ resolved.pathPrefix,
117
+ options,
118
+ diagnostics
119
+ );
68
120
  return diagnostics;
69
121
  }
70
122
  function validate(source, options) {
71
123
  return validateAst(parse(source), options);
72
124
  }
73
125
  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
- }));
126
+ const ZERO = {
127
+ start: { offset: 0, line: 1, column: 1 },
128
+ end: { offset: 0, line: 1, column: 1 }
129
+ };
130
+ const diagnostics = [];
131
+ if (isBuilderDocument(data)) {
132
+ const rec = data;
133
+ for (const key of Object.keys(rec)) {
134
+ if (!BUILDER_ROOT_KEYS.has(key)) {
135
+ diagnostics.push({
136
+ message: `\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u4E0D\u8BA4\u8BC6\u7684\u9876\u5C42\u5B57\u6BB5 "${key}"\uFF08\u4EC5\u652F\u6301 name / dsl / variables\uFF09`,
137
+ severity: "warning",
138
+ range: ZERO,
139
+ path: "/" + key,
140
+ code: "builder-unknown-key"
141
+ });
142
+ }
143
+ }
144
+ if ("name" in rec && typeof rec.name !== "string") {
145
+ diagnostics.push({
146
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "name" \u5E94\u4E3A\u5B57\u7B26\u4E32',
147
+ severity: "error",
148
+ range: ZERO,
149
+ path: "/name",
150
+ code: "builder-envelope"
151
+ });
152
+ }
153
+ if ("variables" in rec && !Array.isArray(rec.variables)) {
154
+ diagnostics.push({
155
+ message: '\u642D\u5EFA\u5DE5\u5177\u6587\u6863\u7684 "variables" \u5E94\u4E3A\u6570\u7EC4',
156
+ severity: "error",
157
+ range: ZERO,
158
+ path: "/variables",
159
+ code: "builder-envelope"
160
+ });
161
+ }
162
+ }
163
+ const card = unwrapCardData(data);
164
+ const checkResult = FeishuCardV2Schema.safeParse(card);
165
+ if (!checkResult.success) {
166
+ const prefix = isBuilderDocument(data) ? "/dsl" : "";
167
+ for (const issue of checkResult.error.issues) {
168
+ diagnostics.push({
169
+ message: issue.message,
170
+ severity: "error",
171
+ range: ZERO,
172
+ path: joinCardPointer(prefix, pointerToString(issue.path))
173
+ });
174
+ }
175
+ }
176
+ return diagnostics;
85
177
  }
86
178
 
87
179
  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.6",
4
4
  "description": "对比 AST 与 Schema 2.0,输出精确定位到源码字符级的诊断信息",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -17,7 +17,7 @@
17
17
  "dist"
18
18
  ],
19
19
  "dependencies": {
20
- "@open-feishu-card/parser": "0.1.2",
20
+ "@open-feishu-card/parser": "0.1.3",
21
21
  "@open-feishu-card/schema": "0.1.5"
22
22
  },
23
23
  "devDependencies": {