openyida 2026.9.2-1 → 2026.9.2-beta.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.
@@ -23,10 +23,30 @@ function fingerprint(value) {
23
23
  return crypto.createHash('sha256').update(normalized).digest('hex');
24
24
  }
25
25
 
26
+ function rawFingerprint(value) {
27
+ if (typeof value !== 'string' || !value) {
28
+ return '';
29
+ }
30
+ return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
31
+ }
32
+
33
+ function positiveSize(value) {
34
+ const size = Number(value);
35
+ return Number.isFinite(size) && size > 0 ? size : 0;
36
+ }
37
+
38
+ function sha256(value) {
39
+ const digest = String(value || '').toLowerCase();
40
+ return /^[a-f0-9]{64}$/.test(digest) ? digest : '';
41
+ }
42
+
26
43
  function createEmptyDisplayInfo() {
27
44
  return {
28
45
  hasYidaCodeCanvas: false,
46
+ hasCodeBundle: false,
29
47
  hasNativeJsx: false,
48
+ codeBundleCount: 0,
49
+ bundleIds: [],
30
50
  runtimeCodeBytes: 0,
31
51
  sourceCodeBytes: 0,
32
52
  compiledCodeBytes: 0,
@@ -34,6 +54,8 @@ function createEmptyDisplayInfo() {
34
54
  componentCount: 0,
35
55
  canvasRuntimeCode: '',
36
56
  canvasSourceCode: '',
57
+ canvasRuntimeSha256: '',
58
+ canvasSourceSha256: '',
37
59
  nativeCompiledCode: '',
38
60
  nativeSourceCode: '',
39
61
  };
@@ -79,12 +101,37 @@ function traverseDisplayNodes(node, info) {
79
101
 
80
102
  if (node.componentName === 'YidaCodeCanvas') {
81
103
  const props = node.props || {};
104
+ const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
105
+ ? props.codeBundle
106
+ : null;
82
107
  info.hasYidaCodeCanvas = true;
83
108
  info.componentCount++;
84
- info.canvasRuntimeCode = props.runtimeCode || '';
85
- info.canvasSourceCode = props.code || '';
109
+ if (typeof props.runtimeCode === 'string' && props.runtimeCode) {
110
+ info.canvasRuntimeCode = props.runtimeCode;
111
+ }
112
+ if (typeof props.code === 'string' && props.code) {
113
+ info.canvasSourceCode = props.code;
114
+ }
86
115
  info.runtimeCodeBytes += codeBytes(props.runtimeCode);
87
116
  info.sourceCodeBytes += codeBytes(props.code);
117
+ if (codeBundle) {
118
+ const bundleId = String(codeBundle.bundleId || '');
119
+ const runtime = codeBundle.runtime && typeof codeBundle.runtime === 'object'
120
+ ? codeBundle.runtime
121
+ : {};
122
+ const source = codeBundle.source && typeof codeBundle.source === 'object'
123
+ ? codeBundle.source
124
+ : {};
125
+ info.hasCodeBundle = true;
126
+ info.codeBundleCount++;
127
+ if (bundleId && !info.bundleIds.includes(bundleId)) {
128
+ info.bundleIds.push(bundleId);
129
+ }
130
+ info.runtimeCodeBytes += positiveSize(runtime.size);
131
+ info.sourceCodeBytes += positiveSize(source.size);
132
+ info.canvasRuntimeSha256 = sha256(runtime.sha256) || info.canvasRuntimeSha256;
133
+ info.canvasSourceSha256 = sha256(source.sha256) || info.canvasSourceSha256;
134
+ }
88
135
  addImportedModules(info.importedModules, props.importedModules);
89
136
  } else if (node.componentName === 'Jsx') {
90
137
  info.hasNativeJsx = true;
@@ -133,7 +180,9 @@ function hasExpectedDisplayComponent(info, publishMode) {
133
180
  return false;
134
181
  }
135
182
  if (publishMode === 'canvas') {
136
- return info.hasYidaCodeCanvas && info.runtimeCodeBytes > 0;
183
+ return info.hasYidaCodeCanvas && (
184
+ info.runtimeCodeBytes > 0 || !!info.canvasRuntimeSha256
185
+ );
137
186
  }
138
187
  return info.hasNativeJsx && info.compiledCodeBytes > 0;
139
188
  }
@@ -144,7 +193,10 @@ function summarizeDisplayPublishInfo(info) {
144
193
  }
145
194
  return {
146
195
  hasYidaCodeCanvas: info.hasYidaCodeCanvas,
196
+ hasCodeBundle: info.hasCodeBundle,
147
197
  hasNativeJsx: info.hasNativeJsx,
198
+ codeBundleCount: info.codeBundleCount,
199
+ bundleIds: info.bundleIds.slice(),
148
200
  runtimeCodeBytes: info.runtimeCodeBytes,
149
201
  sourceCodeBytes: info.sourceCodeBytes,
150
202
  compiledCodeBytes: info.compiledCodeBytes,
@@ -159,8 +211,13 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
159
211
  const displayComponentPresent = hasExpectedDisplayComponent(readbackInfo, publishMode);
160
212
  const readbackArtifact = getPublishArtifact(readbackInfo, publishMode);
161
213
  const expectedArtifact = getPublishArtifact(expectedInfo, publishMode);
162
- const readbackFingerprint = fingerprint(readbackArtifact);
163
- const expectedFingerprint = fingerprint(expectedArtifact);
214
+ const codeBundleReadback = publishMode === 'canvas' && readbackInfo && readbackInfo.hasCodeBundle;
215
+ const readbackFingerprint = codeBundleReadback
216
+ ? readbackInfo.canvasRuntimeSha256
217
+ : fingerprint(readbackArtifact);
218
+ const expectedFingerprint = codeBundleReadback
219
+ ? rawFingerprint(expectedArtifact)
220
+ : fingerprint(expectedArtifact);
164
221
 
165
222
  return {
166
223
  readbackInfo,
@@ -180,6 +237,7 @@ function verifyPublishedContentMatch(readbackSchema, expectedSchemaContent, publ
180
237
  module.exports = {
181
238
  extractDisplayPublishInfo,
182
239
  fingerprint,
240
+ rawFingerprint,
183
241
  hasExpectedDisplayComponent,
184
242
  parseSchemaContent,
185
243
  summarizeDisplayPublishInfo,
@@ -9,6 +9,7 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ const crypto = require('crypto');
12
13
  const fs = require('fs');
13
14
  const path = require('path');
14
15
  const {
@@ -16,6 +17,7 @@ const {
16
17
  triggerLogin,
17
18
  resolveBaseUrl,
18
19
  httpGet,
20
+ httpGetRedirectText,
19
21
  requestWithAutoLogin,
20
22
  } = require('../core/utils');
21
23
  const { t } = require('../core/i18n');
@@ -227,6 +229,122 @@ function resolveSchemaContent(schemaResult) {
227
229
  return parseJsonObject(content);
228
230
  }
229
231
 
232
+ function validateCodeBundleDescriptor(codeBundle) {
233
+ const bundleId = String(codeBundle && codeBundle.bundleId || '');
234
+ const source = codeBundle && codeBundle.source;
235
+ const runtime = codeBundle && codeBundle.runtime;
236
+ const validSha256 = value => /^[a-f0-9]{64}$/.test(String(value || ''));
237
+ const validSize = value => Number.isFinite(Number(value)) && Number(value) > 0
238
+ && Number(value) <= 5 * 1024 * 1024;
239
+ if (!validSha256(bundleId)
240
+ || !source || !runtime
241
+ || !validSha256(source.sha256) || !validSize(source.size)
242
+ || !validSha256(runtime.sha256) || !validSize(runtime.size)
243
+ || Number(source.size) + Number(runtime.size) > 5 * 1024 * 1024) {
244
+ throw new Error('YidaCodeCanvas codeBundle 描述不合法');
245
+ }
246
+ return { bundleId, source, runtime };
247
+ }
248
+
249
+ function collectCodeBundleCanvases(content) {
250
+ const canvases = [];
251
+ function traverse(node) {
252
+ if (Array.isArray(node)) {
253
+ node.forEach(traverse);
254
+ return;
255
+ }
256
+ if (!node || typeof node !== 'object') {
257
+ return;
258
+ }
259
+ if (node.componentName === 'YidaCodeCanvas' && node.id && node.props
260
+ && node.props.codeBundle && typeof node.props.codeBundle === 'object') {
261
+ canvases.push({ props: node.props, descriptor: validateCodeBundleDescriptor(node.props.codeBundle) });
262
+ }
263
+ Object.values(node).forEach(traverse);
264
+ }
265
+ traverse(content);
266
+ return canvases;
267
+ }
268
+
269
+ async function downloadCodeBundleArtifact(appType, formUuid, bundleId, artifact, descriptor, authRef) {
270
+ let responseMetadata = null;
271
+ const expectedContentTypes = artifact === 'source'
272
+ ? ['text/plain']
273
+ : ['application/javascript', 'text/javascript', 'application/x-javascript'];
274
+ const value = await requestWithAutoLogin((auth) => httpGetRedirectText(
275
+ auth.baseUrl,
276
+ `/alibaba/web/${appType}/query/codeBundle/download.json`,
277
+ { formUuid, bundleId, artifact },
278
+ {
279
+ silentStatus: true,
280
+ maxBytes: 5 * 1024 * 1024,
281
+ expectedContentTypes,
282
+ onResponseMetadata: metadata => {
283
+ responseMetadata = metadata;
284
+ },
285
+ }
286
+ ), authRef);
287
+ if (typeof value !== 'string') {
288
+ const message = value && (value.errorMsg || value.message);
289
+ throw new Error(message || `下载 CodeBundle ${artifact} 失败`);
290
+ }
291
+ const size = Buffer.byteLength(value, 'utf8');
292
+ const digest = crypto.createHash('sha256').update(value, 'utf8').digest('hex');
293
+ if (size !== Number(descriptor.size) || digest !== descriptor.sha256) {
294
+ const context = responseMetadata && responseMetadata.context
295
+ ? responseMetadata.context
296
+ : `baseUrl=${authRef && authRef.baseUrl || 'unknown'}`;
297
+ const error = new Error(t('common.code_bundle_integrity_failed', artifact, context));
298
+ error.code = 'CODE_BUNDLE_INTEGRITY_MISMATCH';
299
+ error.details = {
300
+ artifact,
301
+ expectedSize: Number(descriptor.size),
302
+ actualSize: size,
303
+ expectedSha256: descriptor.sha256,
304
+ actualSha256: digest,
305
+ ...(responseMetadata || {}),
306
+ };
307
+ throw error;
308
+ }
309
+ return value;
310
+ }
311
+
312
+ async function resolveCodeBundleSchema(schemaResult, appType, formUuid, authRef) {
313
+ const content = resolveSchemaContent(schemaResult);
314
+ if (!content) {
315
+ return schemaResult;
316
+ }
317
+ const canvases = collectCodeBundleCanvases(content);
318
+ if (canvases.length === 0) {
319
+ return schemaResult;
320
+ }
321
+
322
+ const downloaded = new Map();
323
+ for (const canvas of canvases) {
324
+ const { bundleId, source, runtime } = canvas.descriptor;
325
+ let code = downloaded.get(bundleId);
326
+ if (!code) {
327
+ const [sourceCode, runtimeCode] = await Promise.all([
328
+ downloadCodeBundleArtifact(appType, formUuid, bundleId, 'source', source, authRef),
329
+ downloadCodeBundleArtifact(appType, formUuid, bundleId, 'runtime', runtime, authRef),
330
+ ]);
331
+ code = { sourceCode, runtimeCode };
332
+ downloaded.set(bundleId, code);
333
+ }
334
+ canvas.props.code = code.sourceCode;
335
+ canvas.props.runtimeCode = code.runtimeCode;
336
+ delete canvas.props.codeBundle;
337
+ }
338
+
339
+ if (schemaResult && Object.prototype.hasOwnProperty.call(schemaResult, 'content')) {
340
+ schemaResult.content = typeof schemaResult.content === 'string'
341
+ ? JSON.stringify(content)
342
+ : content;
343
+ return schemaResult;
344
+ }
345
+ return content;
346
+ }
347
+
230
348
  function addImportedModules(target, value) {
231
349
  let modules = value;
232
350
  if (typeof modules === 'string') {
@@ -269,7 +387,10 @@ function extractDisplayPageSummary(schemaResult) {
269
387
 
270
388
  const displayPage = {
271
389
  hasYidaCodeCanvas: false,
390
+ hasCodeBundle: false,
272
391
  hasNativeJsx: false,
392
+ codeBundleCount: 0,
393
+ bundleIds: [],
273
394
  runtimeCodeBytes: 0,
274
395
  sourceCodeBytes: 0,
275
396
  compiledCodeBytes: 0,
@@ -291,10 +412,29 @@ function extractDisplayPageSummary(schemaResult) {
291
412
 
292
413
  if (node.componentName === 'YidaCodeCanvas' && isComponentInstance(node)) {
293
414
  const props = node.props || {};
415
+ const codeBundle = props.codeBundle && typeof props.codeBundle === 'object'
416
+ ? props.codeBundle
417
+ : null;
294
418
  displayPage.hasYidaCodeCanvas = true;
295
419
  displayPage.componentCount++;
296
420
  displayPage.runtimeCodeBytes += codeBytes(props.runtimeCode);
297
421
  displayPage.sourceCodeBytes += codeBytes(props.code);
422
+ if (codeBundle) {
423
+ const source = codeBundle.source && typeof codeBundle.source === 'object'
424
+ ? codeBundle.source
425
+ : {};
426
+ const runtime = codeBundle.runtime && typeof codeBundle.runtime === 'object'
427
+ ? codeBundle.runtime
428
+ : {};
429
+ const bundleId = String(codeBundle.bundleId || '');
430
+ displayPage.hasCodeBundle = true;
431
+ displayPage.codeBundleCount++;
432
+ displayPage.sourceCodeBytes += Number(source.size) || 0;
433
+ displayPage.runtimeCodeBytes += Number(runtime.size) || 0;
434
+ if (bundleId && !displayPage.bundleIds.includes(bundleId)) {
435
+ displayPage.bundleIds.push(bundleId);
436
+ }
437
+ }
298
438
  addImportedModules(displayPage.importedModules, props.importedModules);
299
439
  } else if (node.componentName === 'Jsx' && isComponentInstance(node)) {
300
440
  displayPage.hasNativeJsx = true;
@@ -581,12 +721,15 @@ async function mapLimit(items, limit, iterator) {
581
721
  return results;
582
722
  }
583
723
 
584
- async function fetchSchemaRecord(appType, form, authRef, retries) {
724
+ async function fetchSchemaRecord(appType, form, authRef, retries, options = {}) {
585
725
  let lastError = null;
586
726
  for (let attempt = 0; attempt <= retries; attempt++) {
587
727
  try {
588
728
  const result = await fetchSchema(appType, form.formUuid, authRef);
589
729
  if (isSuccessfulSchemaResult(result)) {
730
+ const resolvedResult = options.resolveCodeBundles
731
+ ? await resolveCodeBundleSchema(result, appType, form.formUuid, authRef)
732
+ : result;
590
733
  const record = {
591
734
  formUuid: form.formUuid,
592
735
  formName: form.formName,
@@ -594,10 +737,10 @@ async function fetchSchemaRecord(appType, form, authRef, retries) {
594
737
  pathName: form.pathName,
595
738
  success: true,
596
739
  attempts: attempt + 1,
597
- fieldSummary: extractFieldSummary(result),
598
- schema: result,
740
+ fieldSummary: extractFieldSummary(resolvedResult),
741
+ schema: resolvedResult,
599
742
  };
600
- const displayPage = extractDisplayPageSummary(result);
743
+ const displayPage = extractDisplayPageSummary(resolvedResult);
601
744
  if (displayPage) {
602
745
  record.displayPage = displayPage;
603
746
  }
@@ -694,6 +837,10 @@ async function runSingle(parsed, authRef) {
694
837
  throwCommandError(errorMsg);
695
838
  }
696
839
 
840
+ if (!parsed.field && !parsed.compact && !parsed.summaryJson) {
841
+ result = await resolveCodeBundleSchema(result, parsed.appType, parsed.formUuid, authRef);
842
+ }
843
+
697
844
  // 保留既有 --field 返回结构;新 contract 仅由 --compact / --resolve-fields 触发。
698
845
  if (parsed.field) {
699
846
  const allFieldNodes = collectFieldNodes(result);
@@ -782,7 +929,9 @@ async function runBatch(parsed, authRef) {
782
929
  info(` 批量获取 ${forms.length} 个表单 Schema,并发 ${parsed.concurrency},重试 ${parsed.retries}`);
783
930
 
784
931
  const records = await mapLimit(forms, parsed.concurrency, async (form) => {
785
- const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries);
932
+ const record = await fetchSchemaRecord(parsed.appType, form, authRef, parsed.retries, {
933
+ resolveCodeBundles: !parsed.summaryJson,
934
+ });
786
935
  if (record.success && parsed.analysisJson) {
787
936
  record.semanticAnalysis = buildSemanticAnalysis(
788
937
  parsed.appType,
@@ -848,5 +997,9 @@ module.exports = {
848
997
  fetchSchemaRecord,
849
998
  collectFieldNodes,
850
999
  findFieldNode,
1000
+ validateCodeBundleDescriptor,
1001
+ collectCodeBundleCanvases,
1002
+ downloadCodeBundleArtifact,
1003
+ resolveCodeBundleSchema,
851
1004
  run,
852
1005
  };
@@ -10,9 +10,9 @@
10
10
  *
11
11
  * 流程:
12
12
  * 1. 读取源文件,通过内置 babel-transform 编译 + UglifyJS 压缩
13
- * 2. 用代码动态构建 Schema,将 source/compiled 填入 actions.module
13
+ * 2. 用代码动态构建 Schema;Canvas 页面拆出 source/runtime 后统一保存
14
14
  * 3. 读取 token session,在写前确认登录态
15
- * 4. 通过 HTTP POST 调用 saveFormSchema,token 失效时尝试 token refresh
15
+ * 4. Canvas 调统一保存接口;普通 JSX 页面仍调用原 saveFormSchema
16
16
  */
17
17
 
18
18
  const fs = require('fs');
@@ -21,6 +21,7 @@ const querystring = require('querystring');
21
21
  const {
22
22
  findProjectRoot,
23
23
  httpPost,
24
+ httpPostMultipart,
24
25
  httpGet,
25
26
  requestWithAutoLogin,
26
27
  } = require('../core/utils');
@@ -58,6 +59,8 @@ const {
58
59
  const SCHEMA_VERSION = 'V5';
59
60
  const DOMAIN_CODE = 'tEXDRG';
60
61
  const PREFIX = '_view';
62
+ const MAX_CODE_BUNDLE_BYTES = 5 * 1024 * 1024;
63
+ const MAX_CANVAS_SCHEMA_SKELETON_BYTES = 1024 * 1024;
61
64
 
62
65
  // ── 参数解析 ─────────────────────────────────────────
63
66
 
@@ -284,6 +287,61 @@ function buildCanvasSchemaContent(sourceCode, runtimeCode, importedModules, form
284
287
  return buildCanvasPageSchemaContent(sourceCode, runtimeCode, importedModules, formUuid);
285
288
  }
286
289
 
290
+ function buildCanvasSavePayload(schemaContent, sourceCode, runtimeCode) {
291
+ if (typeof sourceCode !== 'string' || sourceCode.length === 0
292
+ || typeof runtimeCode !== 'string' || runtimeCode.length === 0) {
293
+ throw new Error('Canvas source/runtime 不能为空');
294
+ }
295
+ const totalCodeBytes = Buffer.byteLength(sourceCode, 'utf8')
296
+ + Buffer.byteLength(runtimeCode, 'utf8');
297
+ if (totalCodeBytes > MAX_CODE_BUNDLE_BYTES) {
298
+ throw new Error('Canvas source/runtime 总大小不能超过 5 MiB');
299
+ }
300
+
301
+ let schema;
302
+ try {
303
+ schema = JSON.parse(schemaContent);
304
+ } catch {
305
+ throw new Error('Canvas Schema 不是有效 JSON');
306
+ }
307
+
308
+ const canvases = [];
309
+ function traverse(node) {
310
+ if (Array.isArray(node)) {
311
+ node.forEach(traverse);
312
+ return;
313
+ }
314
+ if (!node || typeof node !== 'object') {
315
+ return;
316
+ }
317
+ if (node.componentName === 'YidaCodeCanvas' && node.id) {
318
+ canvases.push(node);
319
+ }
320
+ Object.values(node).forEach(traverse);
321
+ }
322
+ traverse(schema);
323
+ if (canvases.length !== 1) {
324
+ throw new Error('Canvas Schema 必须且只能包含一个 YidaCodeCanvas 节点');
325
+ }
326
+
327
+ const canvas = canvases[0];
328
+ canvas.props = canvas.props && typeof canvas.props === 'object' ? canvas.props : {};
329
+ delete canvas.props.code;
330
+ delete canvas.props.runtimeCode;
331
+ delete canvas.props.codeBundle;
332
+
333
+ const skeletonContent = JSON.stringify(schema);
334
+ if (Buffer.byteLength(skeletonContent, 'utf8') > MAX_CANVAS_SCHEMA_SKELETON_BYTES) {
335
+ throw new Error('Canvas Schema 骨架不能超过 1 MiB');
336
+ }
337
+ return {
338
+ canvasNodeId: canvas.id,
339
+ content: skeletonContent,
340
+ sourceCode,
341
+ runtimeCode,
342
+ };
343
+ }
344
+
287
345
 
288
346
  // ── 4. 发送 saveFormSchema 请求 ──────────────────────
289
347
 
@@ -313,6 +371,40 @@ function sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serv
313
371
  return httpPost(authRef.baseUrl, saveSchemaPath, postData, { silentStatus: true });
314
372
  }
315
373
 
374
+ function sendCanvasSaveRequestWithAuth(authRef, payload, appType, formUuid, serverRevision) {
375
+ if (
376
+ !authRef ||
377
+ typeof authRef.baseUrl !== 'string' ||
378
+ authRef.baseUrl.length === 0 ||
379
+ !isTokenAuthRef(authRef)
380
+ ) {
381
+ const authError = new Error('Publish Canvas write authentication is not ready.');
382
+ authError.code = 'PUBLISH_SCHEMA_WRITE_PRECHECK_FAILED';
383
+ return Promise.reject(authError);
384
+ }
385
+ const gmtModified = requireSchemaServerRevision({ gmtModified: serverRevision });
386
+ const savePath = `/alibaba/web/${appType}/query/codeBundle/save.json?_stamp=${Date.now()}`;
387
+ return httpPostMultipart(authRef.baseUrl, savePath, {
388
+ _csrf_token: authRef.csrfToken,
389
+ formUuid,
390
+ gmtModified,
391
+ canvasNodeId: payload.canvasNodeId,
392
+ content: payload.content,
393
+ importSchema: true,
394
+ }, {
395
+ source: {
396
+ content: payload.sourceCode,
397
+ fileName: 'source.jsx',
398
+ contentType: 'text/plain; charset=UTF-8',
399
+ },
400
+ runtime: {
401
+ content: payload.runtimeCode,
402
+ fileName: 'runtime.js',
403
+ contentType: 'application/javascript; charset=UTF-8',
404
+ },
405
+ }, { silentStatus: true });
406
+ }
407
+
316
408
  function walkFiles(dir, results, limit) {
317
409
  if (!fs.existsSync(dir) || results.length >= limit) {
318
410
  return;
@@ -622,6 +714,8 @@ async function runMain(argv) {
622
714
  baseUrl = authRef.baseUrl;
623
715
 
624
716
  let schemaContent;
717
+ let expectedSchemaContent;
718
+ let canvasSavePayload;
625
719
  let serverRevision;
626
720
  if (isCanvas) {
627
721
  try {
@@ -631,7 +725,18 @@ async function runMain(argv) {
631
725
  process.exit(1);
632
726
  }
633
727
  // canvasResult 已在登录前本地编译完成,这里只需据其装配 Schema content。
634
- schemaContent = buildCanvasSchemaContent(sourceCode, canvasResult.runtimeCode, canvasResult.importedModules, formUuid);
728
+ expectedSchemaContent = buildCanvasSchemaContent(
729
+ sourceCode,
730
+ canvasResult.runtimeCode,
731
+ canvasResult.importedModules,
732
+ formUuid
733
+ );
734
+ canvasSavePayload = buildCanvasSavePayload(
735
+ expectedSchemaContent,
736
+ sourceCode,
737
+ canvasResult.runtimeCode
738
+ );
739
+ schemaContent = canvasSavePayload.content;
635
740
  } else {
636
741
  let existingDataSource = null;
637
742
  try {
@@ -644,6 +749,7 @@ async function runMain(argv) {
644
749
  }
645
750
  baseUrl = authRef.baseUrl;
646
751
  schemaContent = buildSchemaContent(sourceCode, compiledCode, formUuid, { existingDataSource });
752
+ expectedSchemaContent = schemaContent;
647
753
  }
648
754
  success(t('publish.schema_built'));
649
755
 
@@ -654,7 +760,15 @@ async function runMain(argv) {
654
760
  label('Source:', sourcePath);
655
761
  label('Compiled:', compiledPath);
656
762
  step(3, t('publish.step_publish'));
657
- const response = await sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision);
763
+ const response = isCanvas
764
+ ? await sendCanvasSaveRequestWithAuth(
765
+ authRef,
766
+ canvasSavePayload,
767
+ appType,
768
+ formUuid,
769
+ serverRevision
770
+ )
771
+ : await sendSaveRequestWithAuth(authRef, schemaContent, appType, formUuid, serverRevision);
658
772
 
659
773
  if (!response || !response.success) {
660
774
  const errorMsg = response ? response.errorMsg || t('common.unknown_error') : t('common.request_failed');
@@ -668,6 +782,9 @@ async function runMain(argv) {
668
782
  const content = response.content || {};
669
783
  const savedFormUuid = content.formUuid || formUuid;
670
784
  const version = content.version || 0;
785
+ const storageMode = isCanvas ? content.storageMode || null : undefined;
786
+ const bundleId = isCanvas ? content.bundleId || null : undefined;
787
+ const fallbackReason = isCanvas ? content.fallbackReason || null : undefined;
671
788
  success(t('publish.schema_published'));
672
789
  label('Form UUID:', savedFormUuid);
673
790
  label('Version:', String(version));
@@ -686,7 +803,7 @@ async function runMain(argv) {
686
803
  appType,
687
804
  savedFormUuid,
688
805
  authRef,
689
- schemaContent,
806
+ expectedSchemaContent,
690
807
  isCanvas ? 'canvas' : 'native'
691
808
  );
692
809
  } catch (healthCheckError) {
@@ -733,7 +850,20 @@ async function runMain(argv) {
733
850
  ['URL', pageUrl],
734
851
  ]);
735
852
  console.log(JSON.stringify(withBrowserHandoff(
736
- { success: true, appType, formUuid: savedFormUuid, version, url: pageUrl, publishMode, healthCheck: healthCheckResult, navOrder: navOrderResult, navOrderWarning },
853
+ {
854
+ success: true,
855
+ appType,
856
+ formUuid: savedFormUuid,
857
+ version,
858
+ url: pageUrl,
859
+ publishMode,
860
+ storageMode,
861
+ bundleId,
862
+ fallbackReason,
863
+ healthCheck: healthCheckResult,
864
+ navOrder: navOrderResult,
865
+ navOrderWarning,
866
+ },
737
867
  pageUrl,
738
868
  { stage: 'publish_page_success', title: savedFormUuid },
739
869
  browserOpenMode
@@ -767,5 +897,7 @@ if (require.main === module) {
767
897
  module.exports.countCustomPageDataSources = countCustomPageDataSources;
768
898
  module.exports.buildSchemaContent = buildSchemaContent;
769
899
  module.exports.buildCanvasSchemaContent = buildCanvasSchemaContent;
900
+ module.exports.buildCanvasSavePayload = buildCanvasSavePayload;
770
901
  module.exports.sendSaveRequestWithAuth = sendSaveRequestWithAuth;
902
+ module.exports.sendCanvasSaveRequestWithAuth = sendCanvasSaveRequestWithAuth;
771
903
  }
@@ -869,6 +869,15 @@ Examples:
869
869
  response_body: ' Response body: {0}',
870
870
  response_detail: ' Response detail: {0}',
871
871
  response_not_json: 'response is not JSON',
872
+ code_bundle_download_failed: 'CodeBundle download failed: {0} ({1})',
873
+ code_bundle_forbidden: 'permission verification or OSS access was denied',
874
+ code_bundle_not_found: 'the file does not exist or has been removed',
875
+ code_bundle_http_error: 'HTTP {0}',
876
+ code_bundle_html_response: 'received an HTML error page; the environment or route may be incorrect',
877
+ code_bundle_json_response: 'the service returned a JSON error response: {0}',
878
+ code_bundle_unexpected_content_type: 'unexpected response Content-Type: {0}',
879
+ code_bundle_invalid_utf8: 'the response is not valid UTF-8 text',
880
+ code_bundle_integrity_failed: 'CodeBundle {0} integrity verification failed ({1})',
872
881
  login_expired: ' Login session expired: {0}',
873
882
  csrf_expired: ' CSRF token expired: {0}',
874
883
  csrf_refreshed: ' csrf_token refreshed',
@@ -840,6 +840,15 @@ openyida - 宜搭命令行工具
840
840
  response_body: ' 响应内容: {0}',
841
841
  response_detail: ' 响应详情: {0}',
842
842
  response_not_json: '响应非 JSON',
843
+ code_bundle_download_failed: 'CodeBundle 下载失败:{0}({1})',
844
+ code_bundle_forbidden: '权限校验或 OSS 访问被拒绝',
845
+ code_bundle_not_found: '文件不存在或已被清理',
846
+ code_bundle_http_error: 'HTTP {0}',
847
+ code_bundle_html_response: '返回了 HTML 错误页,可能是环境或路由错误',
848
+ code_bundle_json_response: '服务端返回 JSON 错误响应:{0}',
849
+ code_bundle_unexpected_content_type: '响应 Content-Type 不符合预期:{0}',
850
+ code_bundle_invalid_utf8: '响应内容不是有效 UTF-8 文本',
851
+ code_bundle_integrity_failed: 'CodeBundle {0} 完整性校验失败({1})',
843
852
  login_expired: ' 检测到登录过期: {0}',
844
853
  csrf_expired: ' 检测到 csrf_token 过期: {0}',
845
854
  csrf_refreshed: ' csrf_token 已刷新',
package/lib/core/utils.js CHANGED
@@ -1080,6 +1080,413 @@ function createNonJsonResponseResult(statusCode, data) {
1080
1080
  };
1081
1081
  }
1082
1082
 
1083
+ function buildRequestUrl(baseUrl, requestPath, queryParams) {
1084
+ const normalizedBaseUrl = String(baseUrl || '').replace(/\/+$/, '') + '/';
1085
+ const requestUrl = new URL(requestPath, normalizedBaseUrl);
1086
+ if (queryParams) {
1087
+ Object.entries(queryParams).forEach(([key, value]) => {
1088
+ if (value !== undefined && value !== null) {
1089
+ requestUrl.searchParams.set(key, String(value));
1090
+ }
1091
+ });
1092
+ }
1093
+ return requestUrl;
1094
+ }
1095
+
1096
+ async function fetchWithTimeout(requestUrl, requestOptions, timeout) {
1097
+ const controller = new AbortController();
1098
+ const timer = setTimeout(() => controller.abort(), timeout || 30000);
1099
+ try {
1100
+ return await fetch(requestUrl, {
1101
+ ...requestOptions,
1102
+ signal: controller.signal,
1103
+ });
1104
+ } catch (error) {
1105
+ if (error && error.name === 'AbortError') {
1106
+ throw new Error(t('common.request_timeout'));
1107
+ }
1108
+ throw error;
1109
+ } finally {
1110
+ clearTimeout(timer);
1111
+ }
1112
+ }
1113
+
1114
+ async function parseJsonFetchResponse(response, options = {}) {
1115
+ const data = await response.text();
1116
+ if (!options.silentStatus) {
1117
+ warn(t('common.http_status', response.status));
1118
+ }
1119
+ if (isHttpRedirectStatus(response.status) || isHttpAuthStatus(response.status)) {
1120
+ return {
1121
+ __needLogin: true,
1122
+ __httpStatus: response.status,
1123
+ __location: response.headers.get('location') || '',
1124
+ };
1125
+ }
1126
+ try {
1127
+ const parsed = JSON.parse(data);
1128
+ if (isLoginExpired(parsed)) {
1129
+ return { __needLogin: true };
1130
+ }
1131
+ if (isCsrfTokenExpired(parsed)) {
1132
+ return { __csrfExpired: true };
1133
+ }
1134
+ return parsed;
1135
+ } catch {
1136
+ if (!options.silentStatus) {
1137
+ warn(t('common.http_response', data.substring(0, 500)));
1138
+ }
1139
+ return createNonJsonResponseResult(response.status, data);
1140
+ }
1141
+ }
1142
+
1143
+ function appendMultipartFile(form, fieldName, file) {
1144
+ if (!file || file.content === undefined || file.content === null) {
1145
+ throw new TypeError(`multipart file ${fieldName} is required`);
1146
+ }
1147
+ const content = Buffer.isBuffer(file.content)
1148
+ ? file.content
1149
+ : Buffer.from(String(file.content), 'utf8');
1150
+ form.append(
1151
+ fieldName,
1152
+ new Blob([content], { type: file.contentType || 'application/octet-stream' }),
1153
+ file.fileName || fieldName
1154
+ );
1155
+ }
1156
+
1157
+ /**
1158
+ * 发送 multipart/form-data POST。Content-Type boundary 由 Node.js FormData 生成。
1159
+ */
1160
+ async function httpPostMultipart(baseUrl, requestPath, fields, files, optionsOrLegacyCookies, maybeOptions) {
1161
+ if (typeof fetch !== 'function' || typeof FormData !== 'function' || typeof Blob !== 'function') {
1162
+ throw new Error('当前 Node.js 环境缺少 fetch/FormData/Blob,请使用 Node.js 18+');
1163
+ }
1164
+ const optionsOverride = resolveRequestOptions(optionsOrLegacyCookies, maybeOptions);
1165
+ const authHeaders = await resolveRequestAuthHeaders(optionsOverride);
1166
+ const form = new FormData();
1167
+ Object.entries(fields || {}).forEach(([key, value]) => {
1168
+ if (value !== undefined && value !== null) {
1169
+ form.append(key, String(value));
1170
+ }
1171
+ });
1172
+ Object.entries(files || {}).forEach(([key, file]) => appendMultipartFile(form, key, file));
1173
+
1174
+ const requestUrl = buildRequestUrl(baseUrl, requestPath);
1175
+ const response = await fetchWithTimeout(requestUrl, {
1176
+ method: 'POST',
1177
+ redirect: 'manual',
1178
+ headers: {
1179
+ Accept: 'application/json, text/plain, */*',
1180
+ Origin: requestUrl.origin,
1181
+ Referer: optionsOverride.referer || requestUrl.origin + '/',
1182
+ 'x-requested-with': 'XMLHttpRequest',
1183
+ ...authHeaders,
1184
+ },
1185
+ body: form,
1186
+ }, optionsOverride.timeout);
1187
+ return parseJsonFetchResponse(response, optionsOverride);
1188
+ }
1189
+
1190
+ async function readFetchResponseBuffer(response, maxBytes, timeout) {
1191
+ const limit = Number(maxBytes);
1192
+ const timeoutMs = Number(timeout) || 30000;
1193
+ const contentLength = Number(response.headers.get('content-length') || 0);
1194
+ if (Number.isFinite(limit) && limit > 0 && contentLength > limit) {
1195
+ const error = new Error(`响应内容超过允许大小 ${limit} bytes`);
1196
+ error.code = 'HTTP_RESPONSE_TOO_LARGE';
1197
+ throw error;
1198
+ }
1199
+
1200
+ if (!response.body || typeof response.body.getReader !== 'function') {
1201
+ let timer;
1202
+ const arrayBuffer = await Promise.race([
1203
+ response.arrayBuffer(),
1204
+ new Promise((resolve, reject) => {
1205
+ void resolve;
1206
+ timer = setTimeout(() => reject(new Error(t('common.request_timeout'))), timeoutMs);
1207
+ }),
1208
+ ]).finally(() => clearTimeout(timer));
1209
+ const buffer = Buffer.from(arrayBuffer);
1210
+ if (Number.isFinite(limit) && limit > 0 && buffer.length > limit) {
1211
+ const error = new Error(`响应内容超过允许大小 ${limit} bytes`);
1212
+ error.code = 'HTTP_RESPONSE_TOO_LARGE';
1213
+ throw error;
1214
+ }
1215
+ return buffer;
1216
+ }
1217
+
1218
+ const chunks = [];
1219
+ let size = 0;
1220
+ const reader = response.body.getReader();
1221
+ let timedOut = false;
1222
+ const timer = setTimeout(() => {
1223
+ timedOut = true;
1224
+ void reader.cancel();
1225
+ }, timeoutMs);
1226
+ try {
1227
+ for (;;) {
1228
+ const { done, value } = await reader.read();
1229
+ if (done) {
1230
+ break;
1231
+ }
1232
+ const chunk = Buffer.from(value);
1233
+ size += chunk.length;
1234
+ if (Number.isFinite(limit) && limit > 0 && size > limit) {
1235
+ await reader.cancel();
1236
+ const error = new Error(`响应内容超过允许大小 ${limit} bytes`);
1237
+ error.code = 'HTTP_RESPONSE_TOO_LARGE';
1238
+ throw error;
1239
+ }
1240
+ chunks.push(chunk);
1241
+ }
1242
+ if (timedOut) {
1243
+ throw new Error(t('common.request_timeout'));
1244
+ }
1245
+ } finally {
1246
+ clearTimeout(timer);
1247
+ reader.releaseLock();
1248
+ }
1249
+ return Buffer.concat(chunks, size);
1250
+ }
1251
+
1252
+ function normalizeResponseContentType(value) {
1253
+ return String(value || '').split(';')[0].trim().toLowerCase();
1254
+ }
1255
+
1256
+ function responseRequestId(response) {
1257
+ return response.headers.get('x-oss-request-id')
1258
+ || response.headers.get('x-request-id')
1259
+ || '';
1260
+ }
1261
+
1262
+ function buildCodeBundleResponseMetadata(initialUrl, currentUrl, response, firstPartyTraceId) {
1263
+ const traceId = firstPartyTraceId
1264
+ || response.headers.get('eagleeye-traceid')
1265
+ || '';
1266
+ const metadata = {
1267
+ baseUrl: initialUrl.origin,
1268
+ finalHost: currentUrl.host,
1269
+ status: response.status,
1270
+ contentType: normalizeResponseContentType(response.headers.get('content-type')),
1271
+ eagleeyeTraceId: traceId,
1272
+ requestId: responseRequestId(response),
1273
+ };
1274
+ metadata.context = [
1275
+ `baseUrl=${metadata.baseUrl}`,
1276
+ `finalHost=${metadata.finalHost}`,
1277
+ `status=${metadata.status}`,
1278
+ `contentType=${metadata.contentType || 'missing'}`,
1279
+ metadata.eagleeyeTraceId ? `eagleeyeTraceId=${metadata.eagleeyeTraceId}` : '',
1280
+ metadata.requestId ? `requestId=${metadata.requestId}` : '',
1281
+ ].filter(Boolean).join(', ');
1282
+ return metadata;
1283
+ }
1284
+
1285
+ function createCodeBundleDownloadError(code, reason, metadata, responsePreview) {
1286
+ const suffix = responsePreview ? `; response=${responsePreview}` : '';
1287
+ const error = new Error(t('common.code_bundle_download_failed', reason, metadata.context) + suffix);
1288
+ error.code = code;
1289
+ error.details = {
1290
+ ...metadata,
1291
+ responsePreview: responsePreview || undefined,
1292
+ };
1293
+ return error;
1294
+ }
1295
+
1296
+ function compactResponsePreview(buffer) {
1297
+ return buffer.toString('utf8').replace(/\s+/g, ' ').trim().substring(0, 500);
1298
+ }
1299
+
1300
+ async function readErrorResponsePreview(response, timeout) {
1301
+ try {
1302
+ return compactResponsePreview(await readFetchResponseBuffer(response, 4096, timeout));
1303
+ } catch {
1304
+ return '';
1305
+ }
1306
+ }
1307
+
1308
+ function looksLikeHtml(buffer) {
1309
+ const prefix = buffer.toString('utf8', 0, Math.min(buffer.length, 256)).trimStart().toLowerCase();
1310
+ return prefix.startsWith('<!doctype html')
1311
+ || prefix.startsWith('<html')
1312
+ || prefix.startsWith('<head')
1313
+ || prefix.startsWith('<body');
1314
+ }
1315
+
1316
+ function expectedContentTypeMatches(actualContentType, expectedContentTypes) {
1317
+ if (!Array.isArray(expectedContentTypes) || expectedContentTypes.length === 0) {
1318
+ return true;
1319
+ }
1320
+ return expectedContentTypes.some(value => normalizeResponseContentType(value) === actualContentType);
1321
+ }
1322
+
1323
+ function jsonResponseReason(preview) {
1324
+ if (!preview) {
1325
+ return t('common.code_bundle_json_response', t('common.unknown_error'));
1326
+ }
1327
+ try {
1328
+ const payload = JSON.parse(preview);
1329
+ const message = payload && (payload.errorMsg || payload.message || payload.throwable || payload.errorCode);
1330
+ return t('common.code_bundle_json_response', message || t('common.unknown_error'));
1331
+ } catch {
1332
+ return t('common.code_bundle_json_response', t('common.unknown_error'));
1333
+ }
1334
+ }
1335
+
1336
+ /**
1337
+ * 获取文本并显式跟随受控 302。鉴权头只发送给初始宜搭域名,不转发到 OSS。
1338
+ */
1339
+ async function httpGetRedirectText(baseUrl, requestPath, queryParams, optionsOrLegacyCookies, maybeOptions) {
1340
+ if (typeof fetch !== 'function') {
1341
+ throw new Error('当前 Node.js 环境缺少 fetch,请使用 Node.js 18+');
1342
+ }
1343
+ const optionsOverride = resolveRequestOptions(optionsOrLegacyCookies, maybeOptions);
1344
+ const authHeaders = await resolveRequestAuthHeaders(optionsOverride);
1345
+ const initialUrl = buildRequestUrl(baseUrl, requestPath, queryParams);
1346
+ const maxRedirects = Number.isInteger(optionsOverride.maxRedirects)
1347
+ ? optionsOverride.maxRedirects
1348
+ : 3;
1349
+ const maxBytes = Number(optionsOverride.maxBytes) || 5 * 1024 * 1024;
1350
+ let currentUrl = initialUrl;
1351
+ let firstPartyTraceId = '';
1352
+
1353
+ for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount++) {
1354
+ const firstPartyRequest = currentUrl.origin === initialUrl.origin;
1355
+ const response = await fetchWithTimeout(currentUrl, {
1356
+ method: 'GET',
1357
+ redirect: 'manual',
1358
+ headers: firstPartyRequest
1359
+ ? {
1360
+ Accept: 'text/plain, application/javascript, */*',
1361
+ Origin: initialUrl.origin,
1362
+ Referer: initialUrl.origin + '/',
1363
+ 'x-requested-with': 'XMLHttpRequest',
1364
+ ...authHeaders,
1365
+ }
1366
+ : {
1367
+ Accept: 'text/plain, application/javascript, */*',
1368
+ },
1369
+ }, optionsOverride.timeout);
1370
+
1371
+ if (firstPartyRequest && response.headers.get('eagleeye-traceid')) {
1372
+ firstPartyTraceId = response.headers.get('eagleeye-traceid');
1373
+ }
1374
+ const metadata = buildCodeBundleResponseMetadata(
1375
+ initialUrl,
1376
+ currentUrl,
1377
+ response,
1378
+ firstPartyTraceId
1379
+ );
1380
+
1381
+ if (isHttpRedirectStatus(response.status)) {
1382
+ const location = response.headers.get('location');
1383
+ if (!location || redirectCount >= maxRedirects) {
1384
+ throw new Error('CodeBundle 下载重定向无效或次数过多');
1385
+ }
1386
+ const nextUrl = new URL(location, currentUrl);
1387
+ if (!['http:', 'https:'].includes(nextUrl.protocol)) {
1388
+ throw new Error('CodeBundle 下载重定向协议不受支持');
1389
+ }
1390
+ if (currentUrl.protocol === 'https:' && nextUrl.protocol !== 'https:') {
1391
+ throw new Error('CodeBundle 下载禁止从 HTTPS 降级到 HTTP');
1392
+ }
1393
+ if (redirectCount === 0 && nextUrl.origin === initialUrl.origin) {
1394
+ return {
1395
+ __needLogin: true,
1396
+ __httpStatus: response.status,
1397
+ __location: location,
1398
+ };
1399
+ }
1400
+ currentUrl = nextUrl;
1401
+ continue;
1402
+ }
1403
+
1404
+ if (firstPartyRequest && response.status === 401) {
1405
+ return { __needLogin: true, __httpStatus: response.status };
1406
+ }
1407
+ if (response.status < 200 || response.status >= 300) {
1408
+ const preview = await readErrorResponsePreview(response, optionsOverride.timeout);
1409
+ if ([401, 403].includes(response.status)) {
1410
+ throw createCodeBundleDownloadError(
1411
+ 'CODE_BUNDLE_DOWNLOAD_FORBIDDEN',
1412
+ t('common.code_bundle_forbidden'),
1413
+ metadata,
1414
+ preview
1415
+ );
1416
+ }
1417
+ if (response.status === 404) {
1418
+ throw createCodeBundleDownloadError(
1419
+ 'CODE_BUNDLE_DOWNLOAD_NOT_FOUND',
1420
+ t('common.code_bundle_not_found'),
1421
+ metadata,
1422
+ preview
1423
+ );
1424
+ }
1425
+ throw createCodeBundleDownloadError(
1426
+ 'CODE_BUNDLE_DOWNLOAD_HTTP_ERROR',
1427
+ t('common.code_bundle_http_error', response.status),
1428
+ metadata,
1429
+ preview
1430
+ );
1431
+ }
1432
+
1433
+ const contentType = metadata.contentType;
1434
+ const htmlResponse = contentType === 'text/html' || contentType === 'application/xhtml+xml';
1435
+ const jsonResponse = contentType === 'application/json' || contentType.endsWith('+json');
1436
+ if (htmlResponse || jsonResponse
1437
+ || !expectedContentTypeMatches(contentType, optionsOverride.expectedContentTypes)) {
1438
+ const preview = await readErrorResponsePreview(response, optionsOverride.timeout);
1439
+ if (htmlResponse) {
1440
+ throw createCodeBundleDownloadError(
1441
+ 'CODE_BUNDLE_DOWNLOAD_HTML_RESPONSE',
1442
+ t('common.code_bundle_html_response'),
1443
+ metadata,
1444
+ preview
1445
+ );
1446
+ }
1447
+ if (jsonResponse) {
1448
+ throw createCodeBundleDownloadError(
1449
+ 'CODE_BUNDLE_DOWNLOAD_JSON_RESPONSE',
1450
+ jsonResponseReason(preview),
1451
+ metadata,
1452
+ preview
1453
+ );
1454
+ }
1455
+ throw createCodeBundleDownloadError(
1456
+ 'CODE_BUNDLE_DOWNLOAD_UNEXPECTED_CONTENT_TYPE',
1457
+ t('common.code_bundle_unexpected_content_type', contentType || 'missing'),
1458
+ metadata,
1459
+ preview
1460
+ );
1461
+ }
1462
+
1463
+ const buffer = await readFetchResponseBuffer(response, maxBytes, optionsOverride.timeout);
1464
+ if (looksLikeHtml(buffer)) {
1465
+ throw createCodeBundleDownloadError(
1466
+ 'CODE_BUNDLE_DOWNLOAD_HTML_RESPONSE',
1467
+ t('common.code_bundle_html_response'),
1468
+ metadata,
1469
+ compactResponsePreview(buffer)
1470
+ );
1471
+ }
1472
+ let value;
1473
+ try {
1474
+ value = new TextDecoder('utf-8', { fatal: true }).decode(buffer);
1475
+ } catch {
1476
+ throw createCodeBundleDownloadError(
1477
+ 'CODE_BUNDLE_DOWNLOAD_INVALID_UTF8',
1478
+ t('common.code_bundle_invalid_utf8'),
1479
+ metadata
1480
+ );
1481
+ }
1482
+ if (typeof optionsOverride.onResponseMetadata === 'function') {
1483
+ optionsOverride.onResponseMetadata(metadata);
1484
+ }
1485
+ return value;
1486
+ }
1487
+ throw new Error('CodeBundle 下载重定向次数过多');
1488
+ }
1489
+
1083
1490
  /**
1084
1491
  * 发送 HTTP POST 请求(application/x-www-form-urlencoded)
1085
1492
  * @param {string} baseUrl
@@ -1504,8 +1911,10 @@ module.exports = {
1504
1911
  isLoginExpired,
1505
1912
  isCsrfTokenExpired,
1506
1913
  httpPost,
1914
+ httpPostMultipart,
1507
1915
  httpPostJson,
1508
1916
  httpGet,
1917
+ httpGetRedirectText,
1509
1918
  requestWithAutoLogin,
1510
1919
  requestNonIdempotentWithAuthPreflight,
1511
1920
  getNpmExecutable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.9.2-1",
3
+ "version": "2026.9.2-beta.1",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",