sy-lowcode-workspace-tools 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,287 @@
1
+ /**
2
+ * sync-schema.mjs - 将 form schema 同步到后端 API
3
+ *
4
+ * 扫描 src/forms/{form}/schema.ts 中的 defineFormSchema,
5
+ * 转换为后端 updateFormSchema API 格式并发送请求。
6
+ *
7
+ * 用法:
8
+ * tsx scripts/sync-schema.mjs [options]
9
+ *
10
+ * 选项:
11
+ * --dry-run 只打印 JSON,不发送请求
12
+ * --form <name> 只同步指定表单(目录名)
13
+ * --help 显示帮助信息
14
+ */
15
+
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import { buildSync } from "esbuild";
19
+ import { loadConfig, getApiBaseUrl, rootDir } from "./utils/load-config.mjs";
20
+ import {
21
+ ensureSchemaFormUuid,
22
+ getOpenApiAccessToken,
23
+ } from "./utils/form-api.mjs";
24
+ import {
25
+ assertSchemaSyncResult,
26
+ transformToApiFormat,
27
+ } from "./utils/schema-transform.mjs";
28
+
29
+ const formsDir = path.join(rootDir, "src/forms");
30
+
31
+ // ---------- CLI 参数解析 ----------
32
+
33
+ function parseArgs(argv) {
34
+ const result = {
35
+ dryRun: false,
36
+ form: "",
37
+ help: false,
38
+ };
39
+
40
+ for (let i = 0; i < argv.length; i++) {
41
+ const arg = argv[i];
42
+
43
+ if (arg === "--help" || arg === "-h") {
44
+ result.help = true;
45
+ continue;
46
+ }
47
+ if (arg === "--dry-run") {
48
+ result.dryRun = true;
49
+ continue;
50
+ }
51
+ if (arg === "--form" && argv[i + 1]) {
52
+ result.form = argv[i + 1];
53
+ i++;
54
+ continue;
55
+ }
56
+ }
57
+
58
+ return result;
59
+ }
60
+
61
+ function printHelp() {
62
+ console.log(`
63
+ sync-schema - 将表单 Schema 同步到后端 API
64
+
65
+ 用法:
66
+ tsx scripts/sync-schema.mjs [options]
67
+
68
+ 选项:
69
+ --dry-run 只打印转换后的 JSON,不发送 API 请求
70
+ --form <name> 只同步指定表单(src/forms/ 下的目录名)
71
+ --help, -h 显示帮助信息
72
+
73
+ 示例:
74
+ tsx scripts/sync-schema.mjs --dry-run
75
+ tsx scripts/sync-schema.mjs --form customer-info
76
+ tsx scripts/sync-schema.mjs --form customer-info --dry-run
77
+ `);
78
+ }
79
+
80
+ // ---------- 表单发现 ----------
81
+
82
+ function discoverForms(filterName) {
83
+ if (!fs.existsSync(formsDir)) {
84
+ console.error(`错误: 找不到表单目录 ${formsDir}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ const entries = fs.readdirSync(formsDir, { withFileTypes: true });
89
+ const formDirs = entries
90
+ .filter((entry) => entry.isDirectory())
91
+ .filter((entry) => {
92
+ if (filterName) return entry.name === filterName;
93
+ return true;
94
+ })
95
+ .filter((entry) => {
96
+ const schemaPath = path.join(formsDir, entry.name, "schema.ts");
97
+ return fs.existsSync(schemaPath);
98
+ });
99
+
100
+ return formDirs.map((entry) => ({
101
+ name: entry.name,
102
+ schemaPath: path.join(formsDir, entry.name, "schema.ts"),
103
+ }));
104
+ }
105
+
106
+ // ---------- Schema 加载 ----------
107
+
108
+ async function loadSchema(schemaPath) {
109
+ const tmpFile = schemaPath.replace(/\.ts$/, ".tmp.mjs");
110
+ try {
111
+ // 使用 esbuild 将 .ts 打包为单文件 .mjs(解析所有依赖),再动态 import
112
+ const result = buildSync({
113
+ entryPoints: [schemaPath],
114
+ bundle: true,
115
+ format: "esm",
116
+ platform: "node",
117
+ target: "node18",
118
+ write: false,
119
+ outfile: "out.mjs",
120
+ });
121
+ const code = result.outputFiles[0].text;
122
+ fs.writeFileSync(tmpFile, code, "utf-8");
123
+ const module = await import(tmpFile);
124
+ return module.default || module;
125
+ } catch (error) {
126
+ console.error(`错误: 无法加载 schema 文件 ${schemaPath}`);
127
+ console.error(` ${error.message}`);
128
+ return null;
129
+ } finally {
130
+ if (fs.existsSync(tmpFile)) {
131
+ fs.unlinkSync(tmpFile);
132
+ }
133
+ }
134
+ }
135
+
136
+ // ---------- API 发送 ----------
137
+
138
+ async function sendToApi(apiPayload, config, accessToken) {
139
+ const apiBase = getApiBaseUrl(config);
140
+ const url = `${apiBase}/dingtalk-api/v1.0/forms/updateFormSchema`;
141
+
142
+ console.log(` 发送请求到: ${url}`);
143
+
144
+ const response = await fetch(url, {
145
+ method: "POST",
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ "x-acs-dingtalk-access-token": accessToken,
149
+ },
150
+ body: JSON.stringify({
151
+ userId: config.userId,
152
+ appType: apiPayload.appType || config.appType,
153
+ formUuid: apiPayload.formUuid,
154
+ schema: apiPayload.schema,
155
+ packages: apiPayload.packages,
156
+ }),
157
+ });
158
+
159
+ let body = null;
160
+ try {
161
+ body = await response.json();
162
+ } catch {
163
+ body = null;
164
+ }
165
+
166
+ if (!response.ok) {
167
+ throw new Error(`API 请求失败: ${response.status} ${response.statusText}`);
168
+ }
169
+ assertSchemaSyncResult(body, apiPayload.fieldCount);
170
+
171
+ return body;
172
+ }
173
+
174
+ // ---------- 主流程 ----------
175
+
176
+ async function main() {
177
+ const args = parseArgs(process.argv.slice(2));
178
+
179
+ if (args.help) {
180
+ printHelp();
181
+ process.exit(0);
182
+ }
183
+
184
+ console.log("🔍 扫描表单 Schema...\n");
185
+
186
+ const forms = discoverForms(args.form);
187
+
188
+ if (forms.length === 0) {
189
+ if (args.form) {
190
+ console.error(`错误: 找不到表单 "${args.form}"`);
191
+ console.error(` 请确认 src/forms/${args.form}/schema.ts 存在`);
192
+ } else {
193
+ console.error("错误: 未发现任何表单 schema 文件");
194
+ }
195
+ process.exit(1);
196
+ }
197
+
198
+ console.log(`发现 ${forms.length} 个表单:\n`);
199
+ forms.forEach((f) => console.log(` - ${f.name}`));
200
+ console.log("");
201
+
202
+ const config = await loadConfig();
203
+ const results = [];
204
+ const createdForms = new Set();
205
+ let accessToken = null;
206
+
207
+ for (const form of forms) {
208
+ console.log(`📋 处理: ${form.name}`);
209
+
210
+ const schema = await loadSchema(form.schemaPath);
211
+ if (!schema) {
212
+ results.push({ name: form.name, success: false, error: "加载失败" });
213
+ continue;
214
+ }
215
+
216
+ try {
217
+ if (!args.dryRun && !accessToken) {
218
+ accessToken = await getOpenApiAccessToken(config);
219
+ }
220
+
221
+ const ensured = await ensureSchemaFormUuid({
222
+ config,
223
+ schemaPath: form.schemaPath,
224
+ formName: form.name,
225
+ accessToken,
226
+ dryRun: args.dryRun,
227
+ });
228
+ schema.formMeta.formUuid = ensured.formUuid;
229
+ schema.formMeta.appType = ensured.appType || schema.formMeta.appType;
230
+ if (ensured.created || ensured.dryRunCreated) {
231
+ createdForms.add(form.name);
232
+ }
233
+ } catch (error) {
234
+ console.error(` ❌ 自动创建表单失败: ${error.message}`);
235
+ results.push({
236
+ name: form.name,
237
+ success: false,
238
+ error: error.message,
239
+ });
240
+ console.log("");
241
+ continue;
242
+ }
243
+
244
+ let apiPayload;
245
+ try {
246
+ apiPayload = transformToApiFormat(schema, form.name);
247
+ } catch (error) {
248
+ console.error(` ❌ Schema 校验失败: ${error.message}`);
249
+ results.push({ name: form.name, success: false, error: error.message });
250
+ console.log("");
251
+ continue;
252
+ }
253
+
254
+ if (args.dryRun) {
255
+ console.log(` [dry-run] 转换结果:`);
256
+ console.log(JSON.stringify(apiPayload, null, 2));
257
+ results.push({ name: form.name, success: true, dryRun: true });
258
+ } else {
259
+ try {
260
+ const response = await sendToApi(apiPayload, config, accessToken);
261
+ console.log(` ✅ 同步成功,tableName=${response.data.tableName}`);
262
+ results.push({ name: form.name, success: true, response });
263
+ } catch (error) {
264
+ console.error(` ❌ 同步失败: ${error.message}`);
265
+ results.push({ name: form.name, success: false, error: error.message });
266
+ }
267
+ }
268
+
269
+ console.log("");
270
+ }
271
+
272
+ // 汇总
273
+ const succeeded = results.filter((r) => r.success).length;
274
+ const created = createdForms.size;
275
+ const failed = results.filter((r) => !r.success).length;
276
+
277
+ console.log("---");
278
+ console.log(
279
+ `完成: ${succeeded} 成功, ${created} 自动创建, ${failed} 失败${args.dryRun ? " (dry-run 模式)" : ""}`,
280
+ );
281
+
282
+ if (failed > 0) {
283
+ process.exit(1);
284
+ }
285
+ }
286
+
287
+ await main();