openyida 2026.7.23-2 → 2026.7.25

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.
@@ -25,7 +25,7 @@
25
25
  * 这些 UMD 依赖由画布运行时依据 importedModules 白名单按需注入。
26
26
  *
27
27
  * 因此本地编译 = Babel 把 JSX/TS 转成 ES5 → 把 import 改写成 window 别名引用、
28
- * 把 export default 改写成 `var YidaComp = ...` → 正则抽出依赖包名。
28
+ * 把 export default 改写成画布入口 `YidaComp` → 正则抽出依赖包名。
29
29
  */
30
30
 
31
31
  const Babel = require('@babel/standalone');
@@ -152,6 +152,15 @@ function esmToWindowPlugin({ types: t }, options = {}) {
152
152
  );
153
153
  }
154
154
 
155
+ function hasProgramBinding(path, name) {
156
+ const program = path.findParent((parentPath) => parentPath.isProgram());
157
+ return Boolean(program && program.scope.hasBinding(name));
158
+ }
159
+
160
+ function isExistingYidaCompDefault(path, decl) {
161
+ return t.isIdentifier(decl, { name: 'YidaComp' }) && hasProgramBinding(path, 'YidaComp');
162
+ }
163
+
155
164
  return {
156
165
  name: 'yida-esm-to-window',
157
166
  visitor: {
@@ -217,6 +226,10 @@ function esmToWindowPlugin({ types: t }, options = {}) {
217
226
  const decl = path.node.declaration;
218
227
  if (t.isFunctionDeclaration(decl) || t.isClassDeclaration(decl)) {
219
228
  if (decl.id) {
229
+ if (decl.id.name === 'YidaComp') {
230
+ path.replaceWith(decl);
231
+ return;
232
+ }
220
233
  // 具名声明:保留声明本体,再补 `var YidaComp = <name>;`
221
234
  const idName = decl.id.name;
222
235
  path.replaceWithMultiple([
@@ -235,6 +248,10 @@ function esmToWindowPlugin({ types: t }, options = {}) {
235
248
  );
236
249
  return;
237
250
  }
251
+ if (isExistingYidaCompDefault(path, decl)) {
252
+ path.remove();
253
+ return;
254
+ }
238
255
  // export default <表达式>
239
256
  path.replaceWith(
240
257
  t.variableDeclaration('var', [t.variableDeclarator(t.identifier('YidaComp'), decl)])
@@ -258,6 +275,32 @@ function esmToWindowPlugin({ types: t }, options = {}) {
258
275
  };
259
276
  }
260
277
 
278
+ function buildCanvasRuntimeWrapper(runtimeCode) {
279
+ return (
280
+ 'return function(iframeWindow, parentWindow){ const window = iframeWindow; '
281
+ + runtimeCode
282
+ + ' return YidaComp; }'
283
+ );
284
+ }
285
+
286
+ function assertCanvasRuntimeParseable(runtimeCode, options = {}) {
287
+ try {
288
+ // 只编译 wrapper,不执行 factory 或用户组件,避免触发源码里的运行期副作用。
289
+ // eslint-disable-next-line no-new-func
290
+ new Function(buildCanvasRuntimeWrapper(runtimeCode));
291
+ } catch (error) {
292
+ const detail = error && error.message ? error.message : String(error);
293
+ const sourceLabel = options.sourcePath ? ` (${options.sourcePath})` : '';
294
+ const compileError = new Error(
295
+ `Code Canvas runtimeCode 无法通过画布运行时装配校验${sourceLabel}: ${detail}`
296
+ + '。请检查默认导出、YidaComp 入口或 window 等运行时保留变量是否重复声明。'
297
+ );
298
+ compileError.code = 'OPENYIDA_CANVAS_RUNTIME_PARSE_FAILED';
299
+ compileError.cause = error;
300
+ throw compileError;
301
+ }
302
+ }
303
+
261
304
  /**
262
305
  * 本地编译 Code Canvas 源码。
263
306
  * @param {string} source 原始 React/JSX/TSX 源码
@@ -322,6 +365,7 @@ function compileCanvasLocal(source, options = {}) {
322
365
  artifact: options.sourcePath ? options.sourcePath + ' runtime' : 'Code Canvas runtime',
323
366
  code: 'OPENYIDA_CANVAS_SOURCE_EMOJI_FORBIDDEN',
324
367
  });
368
+ assertCanvasRuntimeParseable(runtimeCode, options);
325
369
  return {
326
370
  runtimeCode,
327
371
  importedModules: JSON.stringify(importedModules),
@@ -356,5 +400,6 @@ module.exports = {
356
400
  extractImportedModules,
357
401
  resolveWindowAlias,
358
402
  shouldAllowUnsupportedBareImports,
403
+ assertCanvasRuntimeParseable,
359
404
  MODULE_ALIAS_MAP,
360
405
  };
@@ -4334,6 +4334,109 @@ function sanitizeFailureResult(result) {
4334
4334
  return sanitized;
4335
4335
  }
4336
4336
 
4337
+ function getUpdateFormConfigRetryDelays() {
4338
+ const rawDelays = process.env.OPENYIDA_UPDATE_FORM_CONFIG_RETRY_DELAYS_MS;
4339
+ if (rawDelays === undefined) {
4340
+ return [500, 1000];
4341
+ }
4342
+ return String(rawDelays)
4343
+ .split(',')
4344
+ .map(function (part) { return Number(part.trim()); })
4345
+ .filter(function (delay) { return Number.isFinite(delay) && delay >= 0; });
4346
+ }
4347
+
4348
+ function waitForUpdateFormConfigRetry(delayMs) {
4349
+ if (!delayMs) {
4350
+ return Promise.resolve();
4351
+ }
4352
+ return new Promise(function (resolve) {
4353
+ setTimeout(resolve, delayMs);
4354
+ });
4355
+ }
4356
+
4357
+ function getUpdateFormConfigErrorText(result) {
4358
+ if (!result || typeof result !== 'object') {
4359
+ return '';
4360
+ }
4361
+ return [
4362
+ result.errorMsg,
4363
+ result.message,
4364
+ result.error,
4365
+ result.errorCode,
4366
+ result.code,
4367
+ ]
4368
+ .filter(function (part) { return part !== undefined && part !== null; })
4369
+ .map(function (part) { return String(part); })
4370
+ .join(' ');
4371
+ }
4372
+
4373
+ function getUpdateFormConfigWarningMessage(result) {
4374
+ if (!result || typeof result !== 'object') {
4375
+ return t('common.request_failed');
4376
+ }
4377
+ return result.errorMsg || result.message || result.error || result.errorCode || result.code || t('common.unknown_error');
4378
+ }
4379
+
4380
+ function isRetryablePostSaveUpdateConfigResult(result) {
4381
+ if (!result || result.success || result.__needLogin || result.__csrfExpired) {
4382
+ return false;
4383
+ }
4384
+ const status = Number(result.__httpStatus || result.status || result.statusCode);
4385
+ if (status === 400 || status === 401 || status === 403 || status >= 500) {
4386
+ return false;
4387
+ }
4388
+ const text = getUpdateFormConfigErrorText(result);
4389
+ const lowerText = text.toLowerCase();
4390
+ const normalizedText = lowerText.replace(/[\s_-]/g, '');
4391
+ if (/csrf|auth|login|permission|forbidden|unauthori[sz]ed|invalid|param|权限|登录|认证|授权|参数/.test(lowerText)) {
4392
+ return false;
4393
+ }
4394
+ if (text.indexOf('表单') !== -1 && text.indexOf('不存在') !== -1) {
4395
+ return true;
4396
+ }
4397
+ if (text.indexOf('表单') !== -1 && text.indexOf('未找到') !== -1) {
4398
+ return true;
4399
+ }
4400
+ if (
4401
+ normalizedText.indexOf('formnotfound') !== -1 ||
4402
+ normalizedText.indexOf('formnotexist') !== -1 ||
4403
+ normalizedText.indexOf('formdoesnotexist') !== -1
4404
+ ) {
4405
+ return true;
4406
+ }
4407
+ return lowerText.indexOf('not found') !== -1 && /form|formuuid|form uuid/.test(lowerText);
4408
+ }
4409
+
4410
+ async function sendPostSaveUpdateConfigRequest(authRef, appType, formUuid, version) {
4411
+ const retryDelays = getUpdateFormConfigRetryDelays();
4412
+ for (let attemptIndex = 0; attemptIndex <= retryDelays.length; attemptIndex += 1) {
4413
+ let configResult;
4414
+ try {
4415
+ configResult = await requestWithAutoLogin(function (auth) {
4416
+ return sendUpdateConfigRequest(auth.baseUrl, appType, formUuid, version || 1, 0, auth);
4417
+ }, authRef);
4418
+ } catch (err) {
4419
+ return {
4420
+ success: false,
4421
+ errorMsg: err && err.message ? err.message : String(err || 'request failed'),
4422
+ errorCode: err && err.code ? err.code : 'CREATE_FORM_UPDATE_CONFIG_FAILED',
4423
+ };
4424
+ }
4425
+ if (configResult && configResult.success) {
4426
+ return configResult;
4427
+ }
4428
+ if (attemptIndex >= retryDelays.length || !isRetryablePostSaveUpdateConfigResult(configResult)) {
4429
+ return configResult;
4430
+ }
4431
+ await waitForUpdateFormConfigRetry(retryDelays[attemptIndex]);
4432
+ }
4433
+ return {
4434
+ success: false,
4435
+ errorMsg: t('common.request_failed'),
4436
+ errorCode: 'CREATE_FORM_UPDATE_CONFIG_FAILED',
4437
+ };
4438
+ }
4439
+
4337
4440
  function buildCreateFormPostCreateFailurePayload(context) {
4338
4441
  const errorObject = context && context.error;
4339
4442
  const payload = {
@@ -4387,6 +4490,7 @@ function assertNoEmojiInDefinitionFileName(value) {
4387
4490
  // ── 保存 Schema 并更新表单配置(create/update 共用)──
4388
4491
  //
4389
4492
  // 封装了 saveFormSchema + updateFormConfig 两步,以及各自的 302 自动重登录重试。
4493
+ // direct create-form 语义:saveFormSchema 成功后,updateFormConfig 只是后置配置通知。
4390
4494
  // 返回 { saveResult, configResult }。
4391
4495
 
4392
4496
  async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, version, stepOffset, failureContext) {
@@ -4447,9 +4551,7 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
4447
4551
  step(configStep, t('create_form.step_update_config', configStep));
4448
4552
  info(t('create_form.sending_config'));
4449
4553
 
4450
- const configResult = await requestWithAutoLogin(function (auth) {
4451
- return sendUpdateConfigRequest(auth.baseUrl, appType, formUuid, version || 1, 0, auth);
4452
- }, authRef);
4554
+ const configResult = await sendPostSaveUpdateConfigRequest(authRef, appType, formUuid, version);
4453
4555
 
4454
4556
  return { saveResult: saveResult, configResult: configResult };
4455
4557
  }
@@ -4571,31 +4673,29 @@ async function mainCreate(parsedArgs, authRef) {
4571
4673
  parsedArgs.browserOpenMode
4572
4674
  )));
4573
4675
  } else {
4574
- const configErrorMsg = configResult ? configResult.errorMsg || t('common.unknown_error') : t('common.request_failed');
4575
- result(false, t('create_form.config_failed', configErrorMsg), [
4676
+ const configErrorMsg = getUpdateFormConfigWarningMessage(configResult);
4677
+ result(true, t('create_form.create_success'), [
4576
4678
  ['Form UUID', formUuid],
4577
4679
  ['URL', formUrl],
4578
4680
  ]);
4681
+ warn(t('create_form.config_failed', configErrorMsg));
4579
4682
  hint(t('create_form.schema_ok_config_failed'));
4580
- const configError = createCreateFormError(configErrorMsg, 'CREATE_FORM_UPDATE_CONFIG_FAILED', {
4581
- appType,
4582
- formUuid,
4583
- result: sanitizeFailureResult(configResult),
4584
- });
4585
- console.log(JSON.stringify(Object.assign(
4586
- buildCreateFormPostCreateFailurePayload({
4683
+ console.log(JSON.stringify(withBrowserHandoff(
4684
+ {
4685
+ success: true,
4587
4686
  appType,
4588
4687
  formTitle,
4589
4688
  formUuid,
4590
4689
  fieldCount,
4591
- stage: 'updateFormConfig',
4592
- error: configError,
4593
- }),
4594
- {
4595
4690
  url: formUrl,
4691
+ stage: 'updateFormConfig',
4596
4692
  schemaSaved: true,
4597
4693
  configWarning: configErrorMsg,
4598
- }
4694
+ configResult: sanitizeFailureResult(configResult),
4695
+ },
4696
+ formUrl,
4697
+ { stage: 'create_form_success', title: formTitle },
4698
+ parsedArgs.browserOpenMode
4599
4699
  )));
4600
4700
  }
4601
4701
  }
@@ -5517,15 +5617,27 @@ async function mainUpdate(parsedArgs, authRef) {
5517
5617
  parsedArgs.browserOpenMode
5518
5618
  )));
5519
5619
  } else {
5520
- const configErrorMsg = configResult ? configResult.errorMsg || t('common.unknown_error') : t('common.request_failed');
5521
- result(false, t('create_form.config_failed', configErrorMsg), [
5620
+ const configErrorMsg = getUpdateFormConfigWarningMessage(configResult);
5621
+ result(true, t('create_form.update_success'), [
5522
5622
  ['Form UUID', formUuid],
5523
5623
  ['URL', formUrl],
5524
5624
  ['Changes', String(appliedChanges.length)],
5525
5625
  ]);
5626
+ warn(t('create_form.config_failed', configErrorMsg));
5526
5627
  hint(t('create_form.schema_ok_config_failed'));
5527
5628
  console.log(JSON.stringify(withBrowserHandoff(
5528
- { success: true, formUuid, appType, changesApplied: appliedChanges.length, changes: appliedChanges, url: formUrl, configWarning: configErrorMsg },
5629
+ {
5630
+ success: true,
5631
+ formUuid,
5632
+ appType,
5633
+ changesApplied: appliedChanges.length,
5634
+ changes: appliedChanges,
5635
+ url: formUrl,
5636
+ stage: 'updateFormConfig',
5637
+ schemaSaved: true,
5638
+ configWarning: configErrorMsg,
5639
+ configResult: sanitizeFailureResult(configResult),
5640
+ },
5529
5641
  formUrl,
5530
5642
  { stage: 'update_form_success', title: formUuid },
5531
5643
  parsedArgs.browserOpenMode
@@ -38,6 +38,9 @@ function hasEnvTokenCredential(env = process.env) {
38
38
  const BUILDER_CORE_COMMAND_IDS = Object.freeze([
39
39
  'agent-capabilities',
40
40
  'commands',
41
+ 'login',
42
+ 'logout',
43
+ 'auth',
41
44
  'app-list',
42
45
  'list-forms',
43
46
  'get-schema',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.7.23-2",
3
+ "version": "2026.7.25",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -24,6 +24,7 @@ Code Canvas 是宜搭的代码画布自定义页面链路:以 `YidaCodeCanvas`
24
24
  - Canvas 源码写成 `.canvas.jsx` / `.canvas.tsx`,`openyida publish` 会自动走 Canvas 链路。
25
25
  - 页面源码路径按 Bash cwd 选择:从仓库根执行命令时用 `project/pages/src/...`;如果 cwd 已是 `<workspace>/project`,用 `pages/src/...`,不要写成 `project/pages/src/...`。
26
26
  - `runtimeCode` 在宿主页真实 `window` 中执行,入口必须返回 `YidaComp` / `YidaComp.default` / 组件函数。
27
+ - 推荐入口写法是 `function YidaComp(props) { ... }`,或 `const App = ...; export default App;`。CLI 已兼容 `const/let/class YidaComp; export default YidaComp`,但生成新代码时优先避开同名默认导出,减少不同 Canvas 运行态装配器下的重复声明风险。
27
28
  - Canvas 组件没有普通页面实例上下文;数据读写通过 fetch、开放 API、连接器代理或显式 props 数据桥完成。
28
29
  - 第三方依赖走白名单;React、antd、ahooks、d3、recharts、Radix、framer-motion 等可按规则 import。
29
30
  - 宜搭运行态组件通过“原生组件桥 + fallback + 值归一化”接入;不改 `vc-deep-yida` 时,以宿主已存在的 `window.Deep` / `window.DeepYida` 探测为主,`window.YidaNativeComponents` 仅作为兼容入口。需要嵌入门户数据管理视图时探测 `DataManageViews`,并显式传入目标表单 `form.value/formUuid`。
@@ -724,6 +724,11 @@
724
724
  ],
725
725
  "aliases": [
726
726
  "登录态"
727
+ ],
728
+ "command_ids": [
729
+ "agent-capabilities",
730
+ "login",
731
+ "auth"
727
732
  ]
728
733
  },
729
734
  {
@@ -739,6 +744,10 @@
739
744
  ],
740
745
  "aliases": [
741
746
  "登出"
747
+ ],
748
+ "command_ids": [
749
+ "logout",
750
+ "auth"
742
751
  ]
743
752
  },
744
753
  {