@vafast/cli 0.1.1

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/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # @vafast/cli
2
+
3
+ Vafast CLI 工具,提供 API 类型同步等功能。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -D @vafast/cli
9
+ ```
10
+
11
+ ## 命令
12
+
13
+ ### `vafast sync` - 同步 API 类型
14
+
15
+ 从服务端获取 API 契约,生成 TypeScript 类型定义文件。
16
+
17
+ ```bash
18
+ # 基本用法
19
+ npx vafast sync --url http://localhost:3000
20
+
21
+ # 指定输出文件
22
+ npx vafast sync --url http://localhost:3000 --out src/types/api.ts
23
+
24
+ # 指定契约端点(默认 /__contract__)
25
+ npx vafast sync --url http://localhost:3000 --endpoint /api/contract
26
+ ```
27
+
28
+ #### 选项
29
+
30
+ | 选项 | 说明 | 默认值 |
31
+ |------|------|--------|
32
+ | `--url <url>` | 服务端地址(必填) | - |
33
+ | `--out <path>` | 输出文件路径 | `src/api.generated.ts` |
34
+ | `--endpoint <path>` | 契约接口路径 | `/__contract__` |
35
+
36
+ ## 工作流程
37
+
38
+ ### 1. 服务端配置
39
+
40
+ 在 vafast 服务端暴露契约接口:
41
+
42
+ ```typescript
43
+ import { defineRoutes, createContractHandler } from 'vafast'
44
+
45
+ const routes = defineRoutes([
46
+ // 你的路由定义...
47
+ ])
48
+
49
+ // 添加契约接口
50
+ const allRoutes = [
51
+ ...routes,
52
+ {
53
+ method: 'GET',
54
+ path: '/__contract__',
55
+ handler: createContractHandler(routes)
56
+ }
57
+ ]
58
+ ```
59
+
60
+ ### 2. 客户端同步
61
+
62
+ ```bash
63
+ npx vafast sync --url http://localhost:3000
64
+ ```
65
+
66
+ ### 3. 使用生成的类型
67
+
68
+ ```typescript
69
+ import { eden } from '@vafast/api-client'
70
+ import type { Api } from './api.generated'
71
+
72
+ const api = eden<Api>('http://localhost:3000')
73
+
74
+ // 类型安全的调用
75
+ const { data, error } = await api.users.get({ page: 1 })
76
+ ```
77
+
78
+ ## 自动化
79
+
80
+ 在 `package.json` 中配置脚本:
81
+
82
+ ```json
83
+ {
84
+ "scripts": {
85
+ "sync": "vafast sync --url $API_URL",
86
+ "dev": "npm run sync && vite",
87
+ "build": "npm run sync && vite build"
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## 生成的类型示例
93
+
94
+ 输入契约:
95
+
96
+ ```json
97
+ {
98
+ "routes": [
99
+ {
100
+ "method": "GET",
101
+ "path": "/users",
102
+ "schema": { "query": { "type": "object", "properties": { "page": { "type": "number" } } } }
103
+ },
104
+ {
105
+ "method": "POST",
106
+ "path": "/users",
107
+ "schema": { "body": { "type": "object", "properties": { "name": { "type": "string" } } } }
108
+ }
109
+ ]
110
+ }
111
+ ```
112
+
113
+ 生成的类型:
114
+
115
+ ```typescript
116
+ export type Api = {
117
+ users: {
118
+ get: {
119
+ query: { page?: number }
120
+ return: unknown
121
+ }
122
+ post: {
123
+ body: { name?: string }
124
+ return: unknown
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## 注意事项
131
+
132
+ 1. **返回类型**:当前契约不包含返回类型信息,生成的类型中返回值为 `unknown`。如需完整类型推断,建议使用 monorepo 共享路由定义。
133
+
134
+ 2. **服务器必须运行**:执行 `sync` 命令时,服务端必须在运行并暴露契约接口。
135
+
136
+ 3. **不要手动修改**:生成的文件会被覆盖,请勿手动修改。
137
+
138
+ ## License
139
+
140
+ MIT
package/bin/vafast.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import('../dist/cli.js')
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Vafast CLI
3
+ *
4
+ * 命令:
5
+ * - vafast sync --url <url> [--out <path>] 同步 API 类型
6
+ */
7
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,21 @@
1
+ import { syncTypes } from "./sync-DU-mhmR1.js";
2
+ import { cac } from "cac";
3
+
4
+ //#region src/cli.ts
5
+ const cli = cac("vafast");
6
+ cli.command("sync", "从服务端同步 API 类型定义").option("--url <url>", "服务端地址(必填)").option("--out <path>", "输出文件路径", { default: "src/api.generated.ts" }).option("--endpoint <path>", "API Spec 接口路径", { default: "/api-spec" }).action(async (options) => {
7
+ if (!options.url) {
8
+ console.error("❌ 请指定服务端地址:--url <url>");
9
+ process.exit(1);
10
+ }
11
+ await syncTypes({
12
+ url: options.url,
13
+ output: options.out,
14
+ endpoint: options.endpoint
15
+ });
16
+ });
17
+ cli.help();
18
+ cli.version("0.1.0");
19
+ cli.parse();
20
+
21
+ //#endregion
@@ -0,0 +1,9 @@
1
+ /**
2
+ * JSON Schema → TypeScript 类型转换器
3
+ *
4
+ * 将 TypeBox 生成的 JSON Schema 转换为 TypeScript 类型字符串
5
+ */
6
+ /**
7
+ * 将 JSON Schema 转换为 TypeScript 类型字符串
8
+ */
9
+ export declare function schemaToType(schema: unknown): string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * sync 命令实现
3
+ *
4
+ * 从服务端拉取契约并生成 TypeScript 类型定义
5
+ */
6
+ interface SyncOptions {
7
+ url: string;
8
+ output: string;
9
+ endpoint: string;
10
+ }
11
+ /**
12
+ * 同步 API 类型
13
+ */
14
+ export declare function syncTypes(options: SyncOptions): Promise<void>;
15
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @vafast/cli
3
+ *
4
+ * Vafast CLI 工具,提供类型同步等功能
5
+ */
6
+ export { syncTypes } from './commands/sync';
7
+ export { schemaToType } from './codegen/schema-to-type';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { schemaToType, syncTypes } from "./sync-DU-mhmR1.js";
2
+
3
+ export { schemaToType, syncTypes };
@@ -0,0 +1,182 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+
4
+ //#region src/codegen/schema-to-type.ts
5
+ /**
6
+ * 将 JSON Schema 转换为 TypeScript 类型字符串
7
+ */
8
+ function schemaToType(schema) {
9
+ if (!schema || typeof schema !== "object") return "unknown";
10
+ const s = schema;
11
+ if (s.const !== void 0) return JSON.stringify(s.const);
12
+ if (s.enum) return s.enum.map((v) => JSON.stringify(v)).join(" | ");
13
+ if (s.anyOf) return s.anyOf.map(schemaToType).join(" | ");
14
+ if (s.oneOf) return s.oneOf.map(schemaToType).join(" | ");
15
+ if (s.allOf) return s.allOf.map(schemaToType).join(" & ");
16
+ switch (s.type) {
17
+ case "string": return "string";
18
+ case "number":
19
+ case "integer": return "number";
20
+ case "boolean": return "boolean";
21
+ case "null": return "null";
22
+ case "array": return arrayToType(s);
23
+ case "object": return objectToType(s);
24
+ default:
25
+ if (s.properties) return objectToType(s);
26
+ return "unknown";
27
+ }
28
+ }
29
+ /**
30
+ * 数组类型转换
31
+ */
32
+ function arrayToType(schema) {
33
+ if (schema.items) {
34
+ const itemType = schemaToType(schema.items);
35
+ return `${itemType}[]`;
36
+ }
37
+ return "unknown[]";
38
+ }
39
+ /**
40
+ * 对象类型转换
41
+ */
42
+ function objectToType(schema) {
43
+ if (!schema.properties) {
44
+ if (schema.additionalProperties === true) return "Record<string, unknown>";
45
+ if (typeof schema.additionalProperties === "object") {
46
+ const valueType = schemaToType(schema.additionalProperties);
47
+ return `Record<string, ${valueType}>`;
48
+ }
49
+ return "{}";
50
+ }
51
+ const required = new Set(schema.required || []);
52
+ const props = [];
53
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
54
+ const propType = schemaToType(propSchema);
55
+ const optional = required.has(key) ? "" : "?";
56
+ props.push(`${key}${optional}: ${propType}`);
57
+ }
58
+ if (props.length === 0) return "{}";
59
+ return `{ ${props.join("; ")} }`;
60
+ }
61
+
62
+ //#endregion
63
+ //#region src/commands/sync.ts
64
+ /**
65
+ * 同步 API 类型
66
+ */
67
+ async function syncTypes(options) {
68
+ const { url, output, endpoint } = options;
69
+ console.log(`🔄 正在从 ${url}${endpoint} 获取契约...`);
70
+ const contractUrl = new URL(endpoint, url).toString();
71
+ let contract;
72
+ try {
73
+ const response = await fetch(contractUrl);
74
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
75
+ contract = await response.json();
76
+ } catch (error) {
77
+ const err = error instanceof Error ? error : new Error(String(error));
78
+ console.error(`❌ 获取契约失败: ${err.message}`);
79
+ process.exit(1);
80
+ }
81
+ console.log(`✅ 获取到 ${contract.routes.length} 个路由`);
82
+ const typeContent = generateTypeDefinition(contract);
83
+ const outputDir = dirname(output);
84
+ mkdirSync(outputDir, { recursive: true });
85
+ writeFileSync(output, typeContent, "utf-8");
86
+ console.log(`✅ 类型定义已生成: ${output}`);
87
+ console.log("");
88
+ console.log("📝 使用方式:");
89
+ console.log(` import { eden } from '@vafast/api-client'`);
90
+ console.log(` import type { Api } from './${output.replace(/\.ts$/, "")}'`);
91
+ console.log(` const api = eden<Api>('${url}')`);
92
+ }
93
+ /**
94
+ * 生成类型定义文件内容
95
+ */
96
+ function generateTypeDefinition(contract) {
97
+ const lines = [];
98
+ lines.push("/**");
99
+ lines.push(" * 自动生成的 API 类型定义");
100
+ lines.push(` * 生成时间: ${contract.generatedAt}`);
101
+ lines.push(` * 版本: ${contract.version}`);
102
+ lines.push(" * ");
103
+ lines.push(" * ⚠️ 请勿手动修改此文件,使用 `vafast sync` 重新生成");
104
+ lines.push(" */");
105
+ lines.push("");
106
+ const routeTree = buildRouteTree(contract.routes);
107
+ lines.push("export type Api = {");
108
+ lines.push(generateRouteTreeType(routeTree, 1));
109
+ lines.push("}");
110
+ lines.push("");
111
+ return lines.join("\n");
112
+ }
113
+ /**
114
+ * 构建路由树
115
+ */
116
+ function buildRouteTree(routes) {
117
+ const root = /* @__PURE__ */ new Map();
118
+ for (const route of routes) {
119
+ const segments = route.path.split("/").filter(Boolean);
120
+ let current = root;
121
+ for (let i = 0; i < segments.length; i++) {
122
+ const segment = segments[i];
123
+ const isDynamic = segment.startsWith(":");
124
+ const key = isDynamic ? ":id" : segment;
125
+ if (!current.has(key)) current.set(key, {
126
+ methods: /* @__PURE__ */ new Map(),
127
+ children: /* @__PURE__ */ new Map(),
128
+ isDynamic
129
+ });
130
+ const node = current.get(key);
131
+ if (i === segments.length - 1) node.methods.set(route.method.toLowerCase(), route);
132
+ current = node.children;
133
+ }
134
+ }
135
+ return root;
136
+ }
137
+ /**
138
+ * 生成路由树的类型定义
139
+ */
140
+ function generateRouteTreeType(tree, indent) {
141
+ const lines = [];
142
+ const pad = " ".repeat(indent);
143
+ for (const [key, node] of tree) {
144
+ if (key === ":id") lines.push(`${pad}':id': {`);
145
+ else lines.push(`${pad}${key}: {`);
146
+ for (const [method, route] of node.methods) {
147
+ const methodType = generateMethodType(route);
148
+ if (route.description) lines.push(`${pad} /** ${route.description} */`);
149
+ lines.push(`${pad} ${method}: ${methodType}`);
150
+ }
151
+ if (node.children.size > 0) {
152
+ const childContent = generateRouteTreeType(node.children, indent + 1);
153
+ if (childContent) lines.push(childContent);
154
+ }
155
+ lines.push(`${pad}}`);
156
+ }
157
+ return lines.join("\n");
158
+ }
159
+ /**
160
+ * 生成方法类型
161
+ */
162
+ function generateMethodType(route) {
163
+ const parts = [];
164
+ if (route.schema?.query) {
165
+ const queryType = schemaToType(route.schema.query);
166
+ parts.push(`query: ${queryType}`);
167
+ }
168
+ if (route.schema?.body) {
169
+ const bodyType = schemaToType(route.schema.body);
170
+ parts.push(`body: ${bodyType}`);
171
+ }
172
+ if (route.schema?.params) {
173
+ const paramsType = schemaToType(route.schema.params);
174
+ parts.push(`params: ${paramsType}`);
175
+ }
176
+ parts.push("return: unknown");
177
+ if (parts.length === 1) return `{ ${parts[0]} }`;
178
+ return `{\n ${parts.join("\n ")}\n }`;
179
+ }
180
+
181
+ //#endregion
182
+ export { schemaToType, syncTypes };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@vafast/cli",
3
+ "version": "0.1.1",
4
+ "description": "Vafast CLI - 类型同步和开发工具",
5
+ "type": "module",
6
+ "bin": {
7
+ "vafast": "./bin/vafast.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsdown",
22
+ "dev": "tsdown --watch",
23
+ "test": "vitest run",
24
+ "release": "npm run build && npm run test && npx bumpp && npm publish --access=public"
25
+ },
26
+ "keywords": [
27
+ "vafast",
28
+ "cli",
29
+ "typescript",
30
+ "codegen",
31
+ "api-client"
32
+ ],
33
+ "author": "vafast",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "cac": "^6.7.14"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "tsdown": "^0.2.0",
41
+ "typescript": "^5.4.5",
42
+ "vafast": "^0.5.1",
43
+ "vitest": "^2.0.0"
44
+ }
45
+ }