@open-feishu-card/validator 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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @open-feishu-card/validator
3
+ *
4
+ * 把 Zod Schema 2.0 的校验结果映射回 parser 产出的 AST 节点,
5
+ * 让每条诊断都精确定位到源码的 Line/Column/Range(字符级)。
6
+ */
7
+ import { type JsonNode, type JsonObjectNode, type ParseResult, type Range } from '@open-feishu-card/parser';
8
+ export type DiagnosticSeverity = 'error' | 'warning' | 'information' | 'hint';
9
+ export interface Diagnostic {
10
+ message: string;
11
+ severity: DiagnosticSeverity;
12
+ range: Range;
13
+ /** 引发诊断的 JSON Pointer(如 /elements/0/text),便于自动修复定位 */
14
+ path: string;
15
+ /** 可选的快速修复标识(由 compiler 提供 fix 实现) */
16
+ code?: string;
17
+ }
18
+ /** 按 Zod issue 的 path 在 AST 中查找对应节点 */
19
+ export declare function findNodeAtPointer(ast: JsonObjectNode, pointer: PropertyKey[]): JsonNode | undefined;
20
+ export interface ValidateOptions {
21
+ /** 额外的废弃属性规则(与内置规则合并) */
22
+ deprecatedProps?: Record<string, string>;
23
+ }
24
+ /** 对已解析结果做 Schema 校验并生成诊断 */
25
+ export declare function validateAst(result: ParseResult, options?: ValidateOptions): Diagnostic[];
26
+ /** 一步到位:源码 → 解析 + 校验 */
27
+ export declare function validate(source: string, options?: ValidateOptions): Diagnostic[];
28
+ /** 校验普通 JS 对象(无位置信息时 range 退化为 {0,0}) */
29
+ export declare function validateData(data: unknown): Diagnostic[];
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @open-feishu-card/validator
3
+ *
4
+ * 把 Zod Schema 2.0 的校验结果映射回 parser 产出的 AST 节点,
5
+ * 让每条诊断都精确定位到源码的 Line/Column/Range(字符级)。
6
+ */
7
+ import { parse, } from '@open-feishu-card/parser';
8
+ import { FeishuCardV2Schema } from '@open-feishu-card/schema';
9
+ /** 按 Zod issue 的 path 在 AST 中查找对应节点 */
10
+ export function findNodeAtPointer(ast, pointer) {
11
+ let node = ast;
12
+ for (const seg of pointer) {
13
+ if (node.type === 'object') {
14
+ const prop = node.properties.find((p) => p.key.value === String(seg));
15
+ if (!prop)
16
+ return undefined;
17
+ node = prop.value;
18
+ }
19
+ else if (node.type === 'array') {
20
+ const item = node.items[seg];
21
+ if (!item)
22
+ return undefined;
23
+ node = item;
24
+ }
25
+ else {
26
+ return undefined;
27
+ }
28
+ }
29
+ return node;
30
+ }
31
+ function pointerToString(path) {
32
+ if (path.length === 0)
33
+ return '/';
34
+ return '/' + path.map((s) => String(s).replace(/~/g, '~0').replace(/\//g, '~1')).join('/');
35
+ }
36
+ const DEPRECATED_PROPS = {
37
+ // 常见 1.0 → 2.0 迁移期废弃写法
38
+ i18n_elements: '2.0 请使用 body.elements + config.locale',
39
+ card_link: '2.0 请使用 elements 中的 button/open_url 行为',
40
+ };
41
+ /** 对已解析结果做 Schema 校验并生成诊断 */
42
+ export function validateAst(result, options = {}) {
43
+ const diagnostics = [];
44
+ for (const e of result.errors) {
45
+ diagnostics.push({
46
+ message: `JSON 语法错误: ${e.message}`,
47
+ severity: 'error',
48
+ range: e.range,
49
+ path: '/',
50
+ });
51
+ }
52
+ if (!result.ast || result.data === undefined)
53
+ return diagnostics;
54
+ const checkResult = FeishuCardV2Schema.safeParse(result.data);
55
+ if (!checkResult.success) {
56
+ for (const issue of checkResult.error.issues) {
57
+ const node = findNodeAtPointer(result.ast, issue.path);
58
+ const range = node
59
+ ? node.range
60
+ : { start: result.ast.range.start, end: result.ast.range.start };
61
+ diagnostics.push({
62
+ message: issue.message,
63
+ severity: 'error',
64
+ range,
65
+ path: pointerToString(issue.path),
66
+ });
67
+ }
68
+ }
69
+ // 废弃属性检查(warning 级别,只针对卡片根级字段)
70
+ const deprecated = { ...DEPRECATED_PROPS, ...(options.deprecatedProps ?? {}) };
71
+ for (const prop of result.ast.properties) {
72
+ const hint = deprecated[prop.key.value];
73
+ if (hint) {
74
+ diagnostics.push({
75
+ message: `废弃属性 "${prop.key.value}": ${hint}`,
76
+ severity: 'warning',
77
+ range: prop.key.range,
78
+ path: '/' + prop.key.value,
79
+ code: 'deprecated-prop',
80
+ });
81
+ }
82
+ }
83
+ return diagnostics;
84
+ }
85
+ /** 一步到位:源码 → 解析 + 校验 */
86
+ export function validate(source, options) {
87
+ return validateAst(parse(source), options);
88
+ }
89
+ /** 校验普通 JS 对象(无位置信息时 range 退化为 {0,0}) */
90
+ export function validateData(data) {
91
+ const checkResult = FeishuCardV2Schema.safeParse(data);
92
+ if (checkResult.success)
93
+ return [];
94
+ return checkResult.error.issues.map((issue) => ({
95
+ message: issue.message,
96
+ severity: 'error',
97
+ range: {
98
+ start: { offset: 0, line: 1, column: 1 },
99
+ end: { offset: 0, line: 1, column: 1 },
100
+ },
101
+ path: pointerToString(issue.path),
102
+ }));
103
+ }
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@open-feishu-card/validator",
3
+ "version": "0.1.0",
4
+ "description": "对比 AST 与 Schema 2.0,输出精确定位到源码字符级的诊断信息",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@open-feishu-card/schema": "0.1.0",
19
+ "@open-feishu-card/parser": "0.1.0"
20
+ },
21
+ "devDependencies": {
22
+ "tsx": "^4.19.3",
23
+ "typescript": "^7.0.2",
24
+ "@types/node": "^22.20.1"
25
+ },
26
+ "license": "MIT",
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/ztxtxwd/open-feishu-card-monorepo.git",
33
+ "directory": "packages/validator"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "typecheck": "tsc --noEmit -p tsconfig.json",
38
+ "test": "tsx --test test/*.test.ts"
39
+ }
40
+ }