openyida 0.1.2 → 1.0.0-beta.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.
Files changed (61) hide show
  1. package/README.md +68 -38
  2. package/bin/yida.js +164 -761
  3. package/lib/babel-transform/index.js +244 -0
  4. package/lib/babel-transform/jsx-utils.js +89 -0
  5. package/lib/check-update.js +72 -0
  6. package/lib/copy.js +258 -0
  7. package/lib/create-app.js +174 -0
  8. package/lib/create-form.js +2244 -0
  9. package/lib/create-page.js +89 -0
  10. package/lib/env.js +164 -0
  11. package/lib/get-page-config.js +102 -0
  12. package/lib/get-schema.js +76 -0
  13. package/lib/login.js +323 -0
  14. package/lib/publish.js +610 -0
  15. package/lib/save-share-config.js +268 -0
  16. package/lib/update-form-config.js +237 -0
  17. package/lib/utils.js +443 -0
  18. package/lib/verify-short-url.js +279 -0
  19. package/package.json +20 -7
  20. package/project/.cache/demo-schema.json +2353 -0
  21. package/project/pages/src/demo-birthday-game.js +833 -0
  22. package/project/pages/src/demo-future-vision-2026.js +1102 -0
  23. package/project/pages/src/demo-salary-calculator.js +904 -0
  24. package/project/prd/demo-birthday-game.md +39 -0
  25. package/project/prd/demo-future-vision-2026.md +78 -0
  26. package/project/prd/demo-salary-calculator.md +101 -0
  27. package/scripts/postinstall.js +114 -0
  28. package/yida-skills/SKILL.md +273 -0
  29. package/yida-skills/reference/association-form-field.md +469 -0
  30. package/yida-skills/reference/employee-field.md +17 -0
  31. package/yida-skills/reference/model-api.md +73 -0
  32. package/yida-skills/reference/serial-number-field.md +132 -0
  33. package/yida-skills/reference/yida-api.md +1208 -0
  34. package/yida-skills/skills/yida-app/SKILL.md +394 -0
  35. package/yida-skills/skills/yida-create-app/SKILL.md +158 -0
  36. package/yida-skills/skills/yida-create-form-page/SKILL.md +598 -0
  37. package/yida-skills/skills/yida-create-page/SKILL.md +103 -0
  38. package/yida-skills/skills/yida-custom-page/SKILL.md +533 -0
  39. package/yida-skills/skills/yida-get-schema/SKILL.md +90 -0
  40. package/yida-skills/skills/yida-login/SKILL.md +200 -0
  41. package/yida-skills/skills/yida-logout/SKILL.md +58 -0
  42. package/yida-skills/skills/yida-page-config/SKILL.md +261 -0
  43. package/yida-skills/skills/yida-publish-page/SKILL.md +113 -0
  44. package/.eslintrc.json +0 -25
  45. package/.github/workflows/ci.yml +0 -123
  46. package/.github/workflows/publish.yml +0 -105
  47. package/.github/workflows/update-contributors.yml +0 -151
  48. package/.openclaw/skills/yida-issue/SKILL.md +0 -27
  49. package/.openclaw/skills/yida-issue/scripts/create-issue.js +0 -317
  50. package/CLAUDE.md +0 -168
  51. package/CONTRIBUTING.md +0 -59
  52. package/install-skills.ps1 +0 -162
  53. package/install-skills.sh +0 -175
  54. package/pages/dist/.gitkeep +0 -0
  55. package/pages/src/.gitkeep +0 -0
  56. package/prd/salary-calculator.md +0 -15
  57. package/tests/cli.test.js +0 -930
  58. package/tests/install.test.js +0 -277
  59. package/tests/yida-issue.test.js +0 -314
  60. /package/{config.json → project/config.json} +0 -0
  61. /package/{.cache → project/pages/dist}/.gitkeep +0 -0
package/lib/publish.js ADDED
@@ -0,0 +1,610 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * publish.js - 宜搭自定义页面发布工具(Node.js 版)
4
+ *
5
+ * 用法:
6
+ * node publish.js <appType> <formUuid> <源文件路径>
7
+ *
8
+ * 示例:
9
+ * node publish.js APP_XXX FORM-XXX pages/xxx.js
10
+ *
11
+ * 流程:
12
+ * 1. 读取源文件,通过 @ali/vu-babel-transform 编译 + UglifyJS 压缩
13
+ * 2. 用代码动态构建 Schema,将 source/compiled 填入 actions.module
14
+ * 3. 读取本地 .cache/cookies.json 获取登录态;若未登录或接口返回 302,则调用 login.py 重新登录
15
+ * 4. 通过 HTTP POST 调用 saveFormSchema 接口发布 Schema
16
+ */
17
+
18
+ const fs = require("fs");
19
+ const path = require("path");
20
+ const { execSync } = require("child_process");
21
+ const https = require("https");
22
+ const http = require("http");
23
+ const querystring = require("querystring");
24
+ const { default: babelTransform } = require("./babel-transform");
25
+ const UglifyJS = require("uglify-js");
26
+ const { findProjectRoot, loadCookieData, triggerLogin, refreshCsrfToken, isLoginExpired, isCsrfTokenExpired } = require('./utils');
27
+
28
+ // ── 配置读取 ──────────────────────────────────────────
29
+ const CONFIG = fs.existsSync(path.resolve(findProjectRoot(), "config.json")) ? JSON.parse(fs.readFileSync(path.resolve(findProjectRoot(), "config.json"), "utf-8")) : {};
30
+ const DEFAULT_BASE_URL = CONFIG.defaultBaseUrl || "https://www.aliwork.com";
31
+ const SCHEMA_VERSION = "V5";
32
+ const DOMAIN_CODE = "tEXDRG";
33
+ const PREFIX = "_view";
34
+ const PROJECT_ROOT = findProjectRoot();
35
+
36
+ // ── 参数解析 ─────────────────────────────────────────
37
+
38
+ function parseArgs() {
39
+ const args = process.argv.slice(2);
40
+ if (args.length < 3) {
41
+ console.error("用法: node publish.js <appType> <formUuid> <源文件路径>");
42
+ console.error("示例:node publish.js APP_XXX FORM-XXX pages/src/xxx.js");
43
+ process.exit(1);
44
+ }
45
+ return {
46
+ appType: args[0],
47
+ formUuid: args[1],
48
+ sourceFile: args[2],
49
+ };
50
+ }
51
+
52
+ // ── 从登录态解析 baseUrl ─────────────────────────────
53
+
54
+ function resolveBaseUrl(loginResult) {
55
+ return (loginResult.base_url || DEFAULT_BASE_URL).replace(/\/+$/, "");
56
+ }
57
+
58
+ // ── 1. 编译源码 ──────────────────────────────────────
59
+
60
+ function compileSource(sourcePath) {
61
+ const sourceFileName = path.basename(sourcePath);
62
+ const parsedPath = path.parse(sourcePath);
63
+ const compiledFileName = `${parsedPath.name}.js`;
64
+ const compiledPath = path.join(findProjectRoot(), "pages", "dist", compiledFileName);
65
+
66
+ console.error(`[1/4] 读取 ${sourceFileName} 源码...`);
67
+ const sourceCode = fs.readFileSync(sourcePath, "utf-8");
68
+
69
+ console.error(`[2/4] Babel 编译 ${sourceFileName}...`);
70
+ const babelResult = babelTransform(sourceCode, {}, false, { RE_VERSION: "7.4.0" });
71
+ if (babelResult.error instanceof Error) {
72
+ const err = babelResult.error;
73
+ let errorMsg = ` ❌ 编译失败:${err.message}`;
74
+
75
+ if (err.loc) {
76
+ errorMsg += `\n 位置: 第 ${err.loc.line} 行, 第 ${err.loc.column} 列`;
77
+ }
78
+ if (err.code) {
79
+ errorMsg += `\n 错误码: ${err.code}`;
80
+ }
81
+
82
+ console.error(errorMsg);
83
+ process.exit(1);
84
+ }
85
+
86
+ console.error(`[3/4] UglifyJS 压缩 → ${compiledFileName}...`);
87
+ const uglifyResult = UglifyJS.minify(babelResult.compiled);
88
+ if (uglifyResult.error) {
89
+ console.error(` 压缩失败:${uglifyResult.error.message}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ // 确保输出目录存在
94
+ const outputDir = path.dirname(compiledPath);
95
+ if (!fs.existsSync(outputDir)) {
96
+ fs.mkdirSync(outputDir, { recursive: true });
97
+ }
98
+
99
+ fs.writeFileSync(compiledPath, uglifyResult.code, "utf-8");
100
+ console.error(` ✅ 编译压缩完成:${compiledPath}`);
101
+
102
+ return { sourceCode, compiledCode: uglifyResult.code };
103
+ }
104
+
105
+ // ── ID 生成工具 ──────────────────────────────────────
106
+
107
+ let nodeIdCounter = 1;
108
+
109
+ function nextNodeId() {
110
+ return "node_oc" + Date.now().toString(36) + (nodeIdCounter++).toString(36);
111
+ }
112
+
113
+ function generateSuffix() {
114
+ return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
115
+ }
116
+
117
+ // ── 2. 构建 Schema ──────────────────────────────────
118
+
119
+ function buildSchemaContent(sourceCode, compiledCode, formUuid) {
120
+ console.error("[4/4] 构建 Schema...");
121
+
122
+ // 构造函数代码(固定模板)
123
+ const constructorCode = "function constructor() {\nvar module = { exports: {} };\nvar _this = this;\nthis.__initMethods__(module.exports, module);\nObject.keys(module.exports).forEach(function(item) {\n if(typeof module.exports[item] === 'function'){\n _this[item] = module.exports[item];\n }\n});\n\n}";
124
+
125
+ // 全局数据源 fit 函数(固定模板)
126
+ const fitCompiled = "'use strict';\n\nvar __preParser__ = function fit(response) {\n var content = response.content !== undefined ? response.content : response;\n var error = {\n message: response.errorMsg || response.errors && response.errors[0] && response.errors[0].msg || response.content || '远程数据源请求出错,success is false'\n };\n var success = true;\n if (response.success !== undefined) {\n success = response.success;\n } else if (response.hasError !== undefined) {\n success = !response.hasError;\n }\n return {\n content: content,\n success: success,\n error: error\n };\n};";
127
+ const fitSource = "function fit(response) {\r\n const content = (response.content !== undefined) ? response.content : response;\r\n const error = {\r\n message: response.errorMsg ||\r\n (response.errors && response.errors[0] && response.errors[0].msg) ||\r\n response.content || '远程数据源请求出错,success is false',\r\n };\r\n let success = true;\r\n if (response.success !== undefined) {\r\n success = response.success;\r\n } else if (response.hasError !== undefined) {\r\n success = !response.hasError;\r\n }\r\n return {\r\n content,\r\n success,\r\n error,\r\n };\r\n}";
128
+
129
+ const schema = {
130
+ schemaType: "superform",
131
+ schemaVersion: "5.0",
132
+ pages: [
133
+ {
134
+ utils: [
135
+ {
136
+ name: "legaoBuiltin",
137
+ type: "npm",
138
+ content: {
139
+ package: "@ali/vu-legao-builtin",
140
+ version: "3.0.0",
141
+ exportName: "legaoBuiltin",
142
+ },
143
+ },
144
+ {
145
+ name: "yidaPlugin",
146
+ type: "npm",
147
+ content: {
148
+ package: "@ali/vu-yida-plugin",
149
+ version: "1.1.0",
150
+ exportName: "yidaPlugin",
151
+ },
152
+ },
153
+ ],
154
+ componentsMap: [
155
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "RootHeader" },
156
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "Jsx" },
157
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "RootContent" },
158
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "RootFooter" },
159
+ { package: "@ali/vc-deep-yida", version: "1.5.169", componentName: "Page" },
160
+ ],
161
+ componentsTree: [
162
+ {
163
+ componentName: "Page",
164
+ id: nextNodeId(),
165
+ props: {
166
+ contentBgColor: "white",
167
+ pageStyle: { backgroundColor: "#f2f3f5" },
168
+ contentMargin: "0",
169
+ contentPadding: "0",
170
+ showTitle: false,
171
+ contentPaddingMobile: "0",
172
+ templateVersion: "1.0.0",
173
+ contentMarginMobile: "0",
174
+ className: "page_" + generateSuffix(),
175
+ contentBgColorMobile: "white",
176
+ },
177
+ condition: true,
178
+ css: "body{background-color:#f2f3f5}",
179
+ methods: {
180
+ __initMethods__: {
181
+ type: "js",
182
+ source: "function (exports, module) { /*set actions code here*/ }",
183
+ compiled: "function (exports, module) { /*set actions code here*/ }",
184
+ },
185
+ },
186
+ dataSource: {
187
+ offline: [],
188
+ globalConfig: {
189
+ fit: {
190
+ compiled: fitCompiled,
191
+ source: fitSource,
192
+ type: "js",
193
+ error: {},
194
+ },
195
+ },
196
+ online: [
197
+ {
198
+ id: "VCB660714833IBHEOXK376TA7XJH2AXUWR8MMW",
199
+ name: "urlParams",
200
+ description: "当前页面地址的参数:如 aliwork.com/APP_XXX/workbench?id=1&name=宜搭,可通过 this.state.urlParams.name 获取到宜搭",
201
+ formUuid: formUuid,
202
+ protocal: "URI",
203
+ isReadonly: true,
204
+ },
205
+ {
206
+ id: "",
207
+ name: "timestamp",
208
+ description: "",
209
+ formUuid: formUuid,
210
+ protocal: "VALUE",
211
+ initialData: "",
212
+ },
213
+ ],
214
+ list: [
215
+ {
216
+ id: "VCB660714833IBHEOXK376TA7XJH2AXUWR8MMW",
217
+ name: "urlParams",
218
+ description: "当前页面地址的参数:如 aliwork.com/APP_XXX/workbench?id=1&name=宜搭,可通过 this.state.urlParams.name 获取到宜搭",
219
+ formUuid: formUuid,
220
+ protocal: "URI",
221
+ isReadonly: true,
222
+ },
223
+ {
224
+ id: "",
225
+ name: "timestamp",
226
+ description: "",
227
+ formUuid: formUuid,
228
+ protocal: "VALUE",
229
+ initialData: "",
230
+ },
231
+ ],
232
+ sync: true,
233
+ },
234
+ lifeCycles: {
235
+ constructor: {
236
+ type: "js",
237
+ compiled: constructorCode,
238
+ source: constructorCode,
239
+ },
240
+ componentWillUnmount: {
241
+ name: "didUnmount",
242
+ id: "didUnmount",
243
+ type: "actionRef",
244
+ params: {},
245
+ },
246
+ componentDidMount: {
247
+ name: "didMount",
248
+ id: "didMount",
249
+ params: {},
250
+ type: "actionRef",
251
+ },
252
+ },
253
+ hidden: false,
254
+ title: "",
255
+ isLocked: false,
256
+ conditionGroup: "",
257
+ children: [
258
+ {
259
+ componentName: "RootHeader",
260
+ id: nextNodeId(),
261
+ props: {},
262
+ condition: true,
263
+ hidden: false,
264
+ title: "",
265
+ isLocked: false,
266
+ conditionGroup: "",
267
+ },
268
+ {
269
+ componentName: "RootContent",
270
+ id: nextNodeId(),
271
+ props: {},
272
+ condition: true,
273
+ hidden: false,
274
+ title: "",
275
+ isLocked: false,
276
+ conditionGroup: "",
277
+ children: [
278
+ {
279
+ componentName: "Jsx",
280
+ id: nextNodeId(),
281
+ props: {
282
+ render: {
283
+ type: "js",
284
+ compiled: "function main(){\n \n \"use strict\";\n\nvar __compiledFunc__ = function render() {\n return this.renderJsx();\n};\n return __compiledFunc__.apply(this, arguments);\n }",
285
+ source: "function render() {\n return this.renderJsx();\n}",
286
+ error: {},
287
+ },
288
+ __style__: {},
289
+ fieldId: "jsx_" + generateSuffix(),
290
+ },
291
+ condition: true,
292
+ hidden: false,
293
+ title: "",
294
+ isLocked: false,
295
+ conditionGroup: "",
296
+ },
297
+ ],
298
+ },
299
+ {
300
+ componentName: "RootFooter",
301
+ id: nextNodeId(),
302
+ props: {},
303
+ condition: true,
304
+ hidden: false,
305
+ title: "",
306
+ isLocked: false,
307
+ conditionGroup: "",
308
+ },
309
+ ],
310
+ },
311
+ ],
312
+ id: formUuid,
313
+ connectComponent: [],
314
+ },
315
+ ],
316
+ // ★ 核心:source 和 compiled 由编译结果动态填入
317
+ actions: {
318
+ module: {
319
+ compiled: compiledCode,
320
+ source: sourceCode,
321
+ },
322
+ type: "FUNCTION",
323
+ list: [
324
+ { id: "getCustomState", title: "getCustomState" },
325
+ { id: "setCustomState", title: "setCustomState" },
326
+ { id: "forceUpdate", title: "forceUpdate" },
327
+ { id: "didMount", title: "didMount" },
328
+ { id: "didUnmount", title: "didUnmount" },
329
+ { id: "renderJsx", title: "renderJsx" },
330
+ ],
331
+ },
332
+ config: {
333
+ connectComponent: [],
334
+ },
335
+ };
336
+
337
+ return JSON.stringify(schema);
338
+ }
339
+
340
+
341
+ const COOKIES_PATH = path.join(PROJECT_ROOT, ".cache", "cookies.json");
342
+
343
+ // ── 4. 发送 saveFormSchema 请求 ──────────────────────
344
+
345
+ function sendSaveRequest(csrfToken, cookies, schemaContent, baseUrl, appType, formUuid) {
346
+ return new Promise((resolve, reject) => {
347
+ const saveSchemaPath = `/alibaba/web/${appType}/${PREFIX}/query/formdesign/saveFormSchema.json?_stamp=${Date.now()}`;
348
+
349
+ const postData = querystring.stringify({
350
+ _csrf_token: csrfToken,
351
+ prefix: PREFIX,
352
+ content: schemaContent,
353
+ formUuid: formUuid,
354
+ schemaVersion: SCHEMA_VERSION,
355
+ domainCode: DOMAIN_CODE,
356
+ importSchema: true,
357
+ });
358
+
359
+ const cookieHeader = cookies
360
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
361
+ .join("; ");
362
+
363
+ const parsedUrl = new URL(baseUrl);
364
+ const isHttps = parsedUrl.protocol === "https:";
365
+ const requestModule = isHttps ? https : http;
366
+
367
+ const requestOptions = {
368
+ hostname: parsedUrl.hostname,
369
+ port: parsedUrl.port || (isHttps ? 443 : 80),
370
+ path: saveSchemaPath,
371
+ method: "POST",
372
+ headers: {
373
+ "Content-Type": "application/x-www-form-urlencoded",
374
+ "Content-Length": Buffer.byteLength(postData),
375
+ Origin: baseUrl,
376
+ Referer: `${baseUrl}/`,
377
+ Cookie: cookieHeader,
378
+ },
379
+ };
380
+
381
+ const request = requestModule.request(requestOptions, (response) => {
382
+ let responseData = "";
383
+ response.on("data", (chunk) => { responseData += chunk; });
384
+ response.on("end", () => {
385
+ console.error(` HTTP 状态码: ${response.statusCode}`);
386
+ let parsed;
387
+ try {
388
+ parsed = JSON.parse(responseData);
389
+ } catch (parseError) {
390
+ console.error(` 响应内容: ${responseData.substring(0, 500)}`);
391
+ resolve({ success: false, errorMsg: `HTTP ${response.statusCode}: 响应非 JSON` });
392
+ return;
393
+ }
394
+ // 检测登录过期(errorCode: "307")
395
+ if (isLoginExpired(parsed)) {
396
+ console.error(` 检测到登录过期: ${parsed.errorMsg}`);
397
+ resolve({ __needLogin: true });
398
+ return;
399
+ }
400
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
401
+ if (isCsrfTokenExpired(parsed)) {
402
+ console.error(` 检测到 csrf_token 过期: ${parsed.errorMsg}`);
403
+ resolve({ __csrfExpired: true });
404
+ return;
405
+ }
406
+ resolve(parsed);
407
+ });
408
+ });
409
+
410
+ request.on("error", (requestError) => { reject(requestError); });
411
+
412
+ request.write(postData);
413
+ request.end();
414
+ });
415
+ }
416
+
417
+ // ── 5. 发送 updateFormConfig 请求 ────────────────────
418
+
419
+ function sendUpdateConfigRequest(csrfToken, cookies, baseUrl, appType, formUuid, version, value) {
420
+ return new Promise((resolve, reject) => {
421
+ const updateConfigPath = `/dingtalk/web/${appType}/query/formdesign/updateFormConfig.json`;
422
+
423
+ const postData = querystring.stringify({
424
+ _csrf_token: csrfToken,
425
+ formUuid: formUuid,
426
+ version: version,
427
+ configType: "MINI_RESOURCE",
428
+ value: value,
429
+ });
430
+
431
+ const cookieHeader = cookies
432
+ .map((cookie) => `${cookie.name}=${cookie.value}`)
433
+ .join("; ");
434
+
435
+ const parsedUrl = new URL(baseUrl);
436
+ const isHttps = parsedUrl.protocol === "https:";
437
+ const requestModule = isHttps ? https : http;
438
+
439
+ const requestOptions = {
440
+ hostname: parsedUrl.hostname,
441
+ port: parsedUrl.port || (isHttps ? 443 : 80),
442
+ path: updateConfigPath,
443
+ method: "POST",
444
+ headers: {
445
+ "Content-Type": "application/x-www-form-urlencoded",
446
+ "Content-Length": Buffer.byteLength(postData),
447
+ Origin: baseUrl,
448
+ Referer: `${baseUrl}/`,
449
+ Cookie: cookieHeader,
450
+ },
451
+ };
452
+
453
+ const request = requestModule.request(requestOptions, (response) => {
454
+ let responseData = "";
455
+ response.on("data", (chunk) => { responseData += chunk; });
456
+ response.on("end", () => {
457
+ console.error(` HTTP 状态码: ${response.statusCode}`);
458
+ let parsed;
459
+ try {
460
+ parsed = JSON.parse(responseData);
461
+ } catch (parseError) {
462
+ console.error(` 响应内容: ${responseData.substring(0, 500)}`);
463
+ resolve({ success: false, errorMsg: `HTTP ${response.statusCode}: 响应非 JSON` });
464
+ return;
465
+ }
466
+ // 检测登录过期(errorCode: "307")
467
+ if (isLoginExpired(parsed)) {
468
+ console.error(` 检测到登录过期: ${parsed.errorMsg}`);
469
+ resolve({ __needLogin: true });
470
+ return;
471
+ }
472
+ // 检测 csrf_token 过期(errorCode: "TIANSHU_000030")
473
+ if (isCsrfTokenExpired(parsed)) {
474
+ console.error(` 检测到 csrf_token 过期: ${parsed.errorMsg}`);
475
+ resolve({ __csrfExpired: true });
476
+ return;
477
+ }
478
+ resolve(parsed);
479
+ });
480
+ });
481
+
482
+ request.on("error", (requestError) => { reject(requestError); });
483
+
484
+ request.write(postData);
485
+ request.end();
486
+ });
487
+ }
488
+
489
+ // ── 主流程 ────────────────────────────────────────────
490
+
491
+ async function main() {
492
+ const { appType, formUuid, sourceFile } = parseArgs();
493
+
494
+ const sourcePath = path.resolve(sourceFile);
495
+ if (!fs.existsSync(sourcePath)) {
496
+ console.error(`❌ 源文件不存在:${sourcePath}`);
497
+ process.exit(1);
498
+ }
499
+
500
+ const parsedSource = path.parse(sourcePath);
501
+ const compiledPath = path.join(findProjectRoot(), "pages", "dist", `${parsedSource.name}.js`);
502
+
503
+ // Step 1: 编译源码 + 构建 Schema
504
+ console.error("\n📦 Step 1: 编译源码 & 构建 Schema\n");
505
+ const { sourceCode, compiledCode } = compileSource(sourcePath);
506
+ const schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid);
507
+ console.error(" ✅ Schema 构建完成!");
508
+
509
+ // Step 2: 读取登录态(优先从本地缓存 Cookie 中提取 csrf_token)
510
+ console.error("\n🔑 Step 2: 读取登录态");
511
+ let cookieData = loadCookieData();
512
+ if (!cookieData || !cookieData.csrf_token) {
513
+ console.error(" ⚠️ 未找到本地登录态或 csrf_token,触发登录...");
514
+ cookieData = triggerLogin();
515
+ }
516
+ let { csrf_token: csrfToken, cookies } = cookieData;
517
+ let baseUrl = resolveBaseUrl(cookieData);
518
+
519
+ console.error("=".repeat(50));
520
+ console.error(" yida-publish - 宜搭页面发布工具");
521
+ console.error("=".repeat(50));
522
+ console.error(`\n 平台地址: ${baseUrl}`);
523
+ console.error(` 应用ID: ${appType}`);
524
+ console.error(` 表单ID: ${formUuid}`);
525
+ console.error(` 源文件: ${sourcePath}`);
526
+ console.error(` 编译产物:${compiledPath}`);
527
+ console.error(` 输出目录:pages/dist/`);
528
+ // Step 3: 发布 Schema(307 时刷新 csrf_token,302 时自动重登录,均自动重试)
529
+ console.error("\n📤 Step 3: 发布 Schema\n");
530
+ let response = await sendSaveRequest(csrfToken, cookies, schemaContent, baseUrl, appType, formUuid);
531
+
532
+ if (response && response.__csrfExpired) {
533
+ cookieData = refreshCsrfToken();
534
+ csrfToken = cookieData.csrf_token;
535
+ cookies = cookieData.cookies;
536
+ baseUrl = resolveBaseUrl(cookieData);
537
+ console.error(" 🔄 重新发送 saveFormSchema 请求(csrf_token 已刷新)...");
538
+ response = await sendSaveRequest(csrfToken, cookies, schemaContent, baseUrl, appType, formUuid);
539
+ }
540
+
541
+ if (response && response.__needLogin) {
542
+ cookieData = triggerLogin();
543
+ csrfToken = cookieData.csrf_token;
544
+ cookies = cookieData.cookies;
545
+ baseUrl = resolveBaseUrl(cookieData);
546
+ console.error(" 🔄 重新发送 saveFormSchema 请求...");
547
+ response = await sendSaveRequest(csrfToken, cookies, schemaContent, baseUrl, appType, formUuid);
548
+ }
549
+
550
+ if (!response || !response.success) {
551
+ const errorMsg = response ? response.errorMsg || "未知错误" : "请求失败";
552
+ console.error(`\n❌ 发布失败: ${errorMsg}`);
553
+ if (response && !response.__needLogin && !response.__csrfExpired) {
554
+ console.error(` 响应详情: ${JSON.stringify(response, null, 2)}`);
555
+ }
556
+ process.exit(1);
557
+ }
558
+
559
+ const content = response.content || {};
560
+ const savedFormUuid = content.formUuid || formUuid;
561
+ const version = content.version || 0;
562
+ console.error(" ✅ Schema 发布成功!");
563
+ console.error(` formUuid: ${savedFormUuid}`);
564
+ console.error(` version: ${version}`);
565
+
566
+ // Step 4: 更新表单配置(307 时刷新 csrf_token,302 时自动重登录,均自动重试)
567
+ console.error("\n⚙️ Step 4: 更新表单配置\n");
568
+ console.error(" 发送 updateFormConfig 请求...");
569
+ let configResponse = await sendUpdateConfigRequest(csrfToken, cookies, baseUrl, appType, savedFormUuid, version, 8);
570
+
571
+ if (configResponse && configResponse.__csrfExpired) {
572
+ cookieData = refreshCsrfToken();
573
+ csrfToken = cookieData.csrf_token;
574
+ cookies = cookieData.cookies;
575
+ baseUrl = resolveBaseUrl(cookieData);
576
+ console.error(" 🔄 重新发送 updateFormConfig 请求(csrf_token 已刷新)...");
577
+ configResponse = await sendUpdateConfigRequest(csrfToken, cookies, baseUrl, appType, savedFormUuid, version, 8);
578
+ }
579
+
580
+ if (configResponse && configResponse.__needLogin) {
581
+ cookieData = triggerLogin();
582
+ csrfToken = cookieData.csrf_token;
583
+ cookies = cookieData.cookies;
584
+ baseUrl = resolveBaseUrl(cookieData);
585
+ console.error(" 🔄 重新发送 updateFormConfig 请求...");
586
+ configResponse = await sendUpdateConfigRequest(csrfToken, cookies, baseUrl, appType, savedFormUuid, version, 8);
587
+ }
588
+
589
+ // 输出结果
590
+ console.error("\n" + "=".repeat(50));
591
+ if (configResponse && configResponse.success) {
592
+ console.error(" ✅ 发布成功!");
593
+ console.error(` formUuid: ${savedFormUuid}`);
594
+ console.error(` version: ${version}`);
595
+ console.error(` 配置已更新: MINI_RESOURCE = 8`);
596
+ } else {
597
+ const errorMsg = configResponse ? configResponse.errorMsg || "未知错误" : "请求失败";
598
+ console.error(` ⚠️ 配置更新失败: ${errorMsg}`);
599
+ console.error(` Schema 已发布,但配置更新失败`);
600
+ if (configResponse && !configResponse.__needLogin && !configResponse.__csrfExpired) {
601
+ console.error(` 响应详情: ${JSON.stringify(configResponse, null, 2)}`);
602
+ }
603
+ }
604
+ console.error("=".repeat(50));
605
+ }
606
+
607
+ main().catch((error) => {
608
+ console.error(`\n❌ 发布异常: ${error.message}`);
609
+ process.exit(1);
610
+ });