openxiangda 1.0.229 → 1.0.231

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 (2) hide show
  1. package/lib/cli.js +410 -47
  2. package/package.json +1 -1
package/lib/cli.js CHANGED
@@ -14212,18 +14212,36 @@ async function resource(args) {
14212
14212
  .map(item => item.formCode || item.form)
14213
14213
  .filter(Boolean)
14214
14214
  );
14215
+ const scopeGrantSourceNeedsInitialSync = (
14216
+ manifest.scopeGrantSources || []
14217
+ ).some(item => item.sync === true);
14215
14218
  const stagedChangeId =
14216
14219
  readStringFlag(flags, 'change') ||
14217
14220
  getStoredChangeBaseline(target, { access: 'reconciliation-read' })
14218
14221
  ?.changeId ||
14219
14222
  '';
14220
- if (workflowFormCodes.length > 0 && stagedChangeId) {
14223
+ const scopeGrantSourceFormCodes =
14224
+ scopeGrantSourceNeedsInitialSync && stagedChangeId
14225
+ ? await resolveScopeGrantSourceFormCodes(
14226
+ config,
14227
+ target,
14228
+ manifest.scopeGrantSources || []
14229
+ )
14230
+ : [];
14231
+ const stagedContractFormCodes = unique([
14232
+ ...workflowFormCodes,
14233
+ ...scopeGrantSourceFormCodes,
14234
+ ]).sort();
14235
+ if (
14236
+ stagedContractFormCodes.length > 0 &&
14237
+ stagedChangeId
14238
+ ) {
14221
14239
  stagedFormBindingOverlays =
14222
14240
  await resolveReusableStagedFormReleaseOverlays(
14223
14241
  config,
14224
14242
  target,
14225
14243
  stagedChangeId,
14226
- { formCodes: workflowFormCodes }
14244
+ { formCodes: stagedContractFormCodes }
14227
14245
  );
14228
14246
  }
14229
14247
  validateWorkspaceResourceBindings(
@@ -14381,6 +14399,7 @@ async function resource(args) {
14381
14399
  replaceManifest: Boolean(flags['replace-manifest']),
14382
14400
  changeId:
14383
14401
  flags.change || getStoredChangeBaseline(target)?.changeId || null,
14402
+ stagedFormBindingOverlays,
14384
14403
  activateFormBundles: Boolean(flags.activate),
14385
14404
  stageBackendReleaseOnly:
14386
14405
  Boolean(flags['stage-only']) &&
@@ -16290,7 +16309,12 @@ function removeDirectStateResource(target, resourceType, code) {
16290
16309
  function normalizeMenuDirectBody(bound, menuItem) {
16291
16310
  const formUuid = resolveManifestFormUuid(bound, menuItem);
16292
16311
  const pageId = resolveManifestPageId(bound, menuItem);
16293
- const parentId = resolveManifestMenuId(bound, menuItem.parentCode) || menuItem.parentId || null;
16312
+ const parentId = requireResolvedMenuParentId(
16313
+ bound,
16314
+ menuItem,
16315
+ new Map(),
16316
+ 'direct menu mutation'
16317
+ );
16294
16318
  return stripUndefinedValues({
16295
16319
  resourceCode: menuItem.code || menuItem.resourceCode,
16296
16320
  name: menuItem.name || menuItem.code,
@@ -19068,7 +19092,19 @@ async function buildResourcePlan(
19068
19092
  ]);
19069
19093
  const actions = [];
19070
19094
  addPlanActions(actions, 'role', manifest.roles, existing.roles, roleEquals);
19071
- addPlanActions(actions, 'menu', manifest.menus, existing.menus, (item, current) => menuEquals(target.bound, item, current));
19095
+ const orderedMenus = orderMenuResourcesForPublish(
19096
+ manifest.menus,
19097
+ target.bound,
19098
+ existing.menus
19099
+ );
19100
+ addPlanActions(
19101
+ actions,
19102
+ 'menu',
19103
+ orderedMenus,
19104
+ existing.menus,
19105
+ (item, current) =>
19106
+ menuEquals(target.bound, item, current, existing.menus)
19107
+ );
19072
19108
  addPlanActions(actions, 'connector', manifest.connectors, existing.connectors, connectorEquals);
19073
19109
  addNotificationPlanActions(target, actions, manifest.notifications || [], existing);
19074
19110
  await addWorkflowPlanActions(config, target, actions, manifest.workflows, existing.workflows);
@@ -22575,21 +22611,61 @@ async function publishFormSettingsResources(
22575
22611
  }
22576
22612
 
22577
22613
  async function publishMenuResources(config, target, menus, result, options = {}) {
22578
- for (const menuItem of menus) {
22579
- if (shouldSkipNoopResource(options, 'menu', menuItem.code)) {
22580
- recordNoopResource(result, 'menu', menuItem.code, options);
22614
+ if (menus.length === 0) return;
22615
+ const existingMenus = await fetchExistingMenusByCode(config, target);
22616
+ const orderedMenus = orderMenuResourcesForPublish(
22617
+ menus,
22618
+ target.bound,
22619
+ existingMenus
22620
+ );
22621
+ for (const menuItem of orderedMenus) {
22622
+ const code = menuResourceCode(menuItem);
22623
+ const plannedAction = getPlannedResourceAction(options, 'menu', code);
22624
+ const existing = existingMenus.get(code);
22625
+ if (existing && !menuPlatformId(existing)) {
22626
+ const error = new Error(
22627
+ `MENU_ONLINE_ID_REQUIRED: 在线 menu ${code} 缺少 menuId,禁止执行恢复发布`
22628
+ );
22629
+ error.code = 'MENU_ONLINE_ID_REQUIRED';
22630
+ throw error;
22631
+ }
22632
+ if (
22633
+ ['noop', 'update'].includes(plannedAction?.action) &&
22634
+ !menuPlatformId(existing)
22635
+ ) {
22636
+ const error = new Error(
22637
+ `MENU_PLAN_TARGET_MISSING: menu ${code} 的冻结计划为 ${plannedAction.action},但写入前在线菜单已不存在`
22638
+ );
22639
+ error.code = 'MENU_PLAN_TARGET_MISSING';
22640
+ throw error;
22641
+ }
22642
+ }
22643
+ for (const menuItem of orderedMenus) {
22644
+ const code = menuResourceCode(menuItem);
22645
+ const existing = existingMenus.get(code);
22646
+ if (shouldSkipNoopResource(options, 'menu', code)) {
22647
+ const existingId = menuPlatformId(existing);
22648
+ saveMenuResource(target, code, existingId, {
22649
+ formUuid: existing.formUuid,
22650
+ pageId: existing.pageId,
22651
+ parentId: existing.parentId,
22652
+ routeCode: existing.routeCode,
22653
+ path: existing.path,
22654
+ });
22655
+ recordNoopResource(result, 'menu', code, options);
22581
22656
  continue;
22582
22657
  }
22583
- const existing = await findExistingMenu(config, target, menuItem.code);
22584
22658
  const formUuid = resolveManifestFormUuid(target.bound, menuItem);
22585
22659
  const pageId = resolveManifestPageId(target.bound, menuItem);
22586
- const parentId =
22587
- resolveManifestMenuId(target.bound, menuItem.parentCode) ||
22588
- menuItem.parentId ||
22589
- null;
22660
+ const parentId = requireResolvedMenuParentId(
22661
+ target.bound,
22662
+ menuItem,
22663
+ existingMenus,
22664
+ `resource publish menu ${code}`
22665
+ );
22590
22666
  const body = {
22591
- resourceCode: menuItem.code,
22592
- name: menuItem.name || menuItem.code,
22667
+ resourceCode: code,
22668
+ name: menuItem.name || code,
22593
22669
  type: menuItem.type || 'nav',
22594
22670
  formUuid,
22595
22671
  pageId,
@@ -22600,11 +22676,12 @@ async function publishMenuResources(config, target, menus, result, options = {})
22600
22676
  routeCode: menuItem.routeCode || null,
22601
22677
  path: menuItem.path || null,
22602
22678
  };
22679
+ const existingId = menuPlatformId(existing);
22603
22680
  let data = existing
22604
22681
  ? await requestWithAuth(
22605
22682
  config,
22606
22683
  target.profileName,
22607
- `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(existing.id)}`,
22684
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(existingId)}`,
22608
22685
  { method: 'POST', body }
22609
22686
  )
22610
22687
  : await requestWithAuth(
@@ -22613,37 +22690,54 @@ async function publishMenuResources(config, target, menus, result, options = {})
22613
22690
  `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`,
22614
22691
  { method: 'POST', body }
22615
22692
  );
22616
- if (data?.id) {
22617
- saveMenuResource(target, menuItem.code, data.id, {
22618
- resourceCode: menuItem.code,
22619
- name: data.name,
22620
- type: data.type,
22621
- ...(formUuid ? { formUuid } : {}),
22622
- ...(pageId ? { pageId } : {}),
22623
- ...(menuItem.pageCode ? { pageCode: menuItem.pageCode } : {}),
22624
- ...(menuItem.routeCode || data.routeCode
22625
- ? { routeCode: data.routeCode || menuItem.routeCode }
22626
- : {}),
22627
- ...(menuItem.path || data.path ? { path: data.path || menuItem.path } : {}),
22628
- });
22693
+ if (!data?.id) {
22694
+ const error = new Error(
22695
+ `MENU_WRITE_ID_REQUIRED: menu ${code} 写入成功响应缺少 menuId,禁止继续发布子菜单`
22696
+ );
22697
+ error.code = 'MENU_WRITE_ID_REQUIRED';
22698
+ throw error;
22629
22699
  }
22630
- result.published.push({ kind: 'menu', code: menuItem.code, action: existing ? 'update' : 'create', id: data?.id });
22700
+ saveMenuResource(target, code, data.id, {
22701
+ resourceCode: code,
22702
+ name: data.name,
22703
+ type: data.type,
22704
+ ...(formUuid ? { formUuid } : {}),
22705
+ ...(pageId ? { pageId } : {}),
22706
+ ...(menuItem.pageCode ? { pageCode: menuItem.pageCode } : {}),
22707
+ ...(menuItem.routeCode || data.routeCode
22708
+ ? { routeCode: data.routeCode || menuItem.routeCode }
22709
+ : {}),
22710
+ ...(menuItem.path || data.path ? { path: data.path || menuItem.path } : {}),
22711
+ parentId: data.parentId || parentId,
22712
+ });
22713
+ existingMenus.set(code, {
22714
+ ...(existing || {}),
22715
+ ...body,
22716
+ ...data,
22717
+ id: data.id,
22718
+ });
22719
+ result.published.push({
22720
+ kind: 'menu',
22721
+ code,
22722
+ action: existing ? 'update' : 'create',
22723
+ id: data?.id,
22724
+ });
22631
22725
  }
22632
22726
  }
22633
22727
 
22634
- async function findExistingMenu(config, target, code) {
22728
+ async function fetchExistingMenusByCode(config, target) {
22635
22729
  const data = await requestWithAuth(
22636
22730
  config,
22637
22731
  target.profileName,
22638
22732
  `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
22639
22733
  );
22640
- const items = flattenItems(normalizeItems(data));
22641
- const stateId = target.bound.resources?.menus?.[code]?.menuId;
22642
- if (stateId) {
22643
- const byState = items.find(item => item.id === stateId);
22644
- if (byState) return byState;
22645
- }
22646
- return items.find(item => item.resourceCode === code);
22734
+ const menus = new Map();
22735
+ indexByCode(
22736
+ menus,
22737
+ flattenItems(normalizeItems(data)),
22738
+ item => item.resourceCode
22739
+ );
22740
+ return menus;
22647
22741
  }
22648
22742
 
22649
22743
  async function publishConnectorResources(config, target, connectors, result, options = {}) {
@@ -23293,7 +23387,84 @@ async function publishScopeDimensionResources(config, target, dimensions, result
23293
23387
  }
23294
23388
  }
23295
23389
 
23390
+ async function resolveScopeGrantSourceFormCodes(
23391
+ config,
23392
+ target,
23393
+ sources = []
23394
+ ) {
23395
+ const formCodes = [];
23396
+ const appendFormCode = value => {
23397
+ const candidate = String(value || '').trim();
23398
+ if (!candidate) return;
23399
+ if (target.bound.resources?.forms?.[candidate]?.formUuid) {
23400
+ formCodes.push(candidate);
23401
+ return;
23402
+ }
23403
+ for (const [code, entry] of Object.entries(
23404
+ target.bound.resources?.forms || {}
23405
+ )) {
23406
+ if (String(entry?.formUuid || '').trim() === candidate) {
23407
+ formCodes.push(code);
23408
+ return;
23409
+ }
23410
+ }
23411
+ };
23412
+ const dimensionCodes = new Set();
23413
+ for (const source of sources || []) {
23414
+ appendFormCode(
23415
+ source.sourceFormCode ||
23416
+ source.formCode ||
23417
+ source.sourceFormUuid ||
23418
+ source.formUuid
23419
+ );
23420
+ for (const mapping of source.dimensionMappings || source.dimensions || []) {
23421
+ const code = String(
23422
+ mapping?.dimensionCode || mapping?.dimension || ''
23423
+ ).trim();
23424
+ if (code) dimensionCodes.add(code);
23425
+ }
23426
+ }
23427
+ if (dimensionCodes.size > 0) {
23428
+ const dimensions = await requestWithAuth(
23429
+ config,
23430
+ target.profileName,
23431
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/dimensions`
23432
+ );
23433
+ for (const dimension of normalizeItems(dimensions)) {
23434
+ const code = String(
23435
+ dimension?.code || dimension?.resourceCode || ''
23436
+ ).trim();
23437
+ if (!dimensionCodes.has(code)) continue;
23438
+ appendFormCode(dimension.sourceFormUuid || dimension.formUuid);
23439
+ }
23440
+ }
23441
+ return unique(formCodes).sort();
23442
+ }
23443
+
23296
23444
  async function publishScopeGrantSourceResources(config, target, sources, result, options = {}) {
23445
+ const stagedFormBindingOverlays =
23446
+ options.stagedFormBindingOverlays instanceof Map
23447
+ ? options.stagedFormBindingOverlays
23448
+ : new Map();
23449
+ let dimensionMap = new Map();
23450
+ if (
23451
+ stagedFormBindingOverlays.size > 0 &&
23452
+ (sources || []).some(item => item.sync === true)
23453
+ ) {
23454
+ const dimensions = await requestWithAuth(
23455
+ config,
23456
+ target.profileName,
23457
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/dimensions`
23458
+ );
23459
+ dimensionMap = new Map(
23460
+ normalizeItems(dimensions)
23461
+ .map(item => [
23462
+ String(item.code || item.resourceCode || '').trim(),
23463
+ item,
23464
+ ])
23465
+ .filter(([code]) => Boolean(code))
23466
+ );
23467
+ }
23297
23468
  for (const item of sources) {
23298
23469
  const code = item.code || item.resourceCode;
23299
23470
  if (shouldSkipNoopResource(options, 'scopeGrantSource', code)) {
@@ -23316,17 +23487,88 @@ async function publishScopeGrantSourceResources(config, target, sources, result,
23316
23487
  id: data?.id,
23317
23488
  };
23318
23489
  if (item.sync === true) {
23319
- published.sync = await requestWithAuth(
23320
- config,
23321
- target.profileName,
23322
- `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/grant-sources/${encodeURIComponent(code)}/sync`,
23323
- { method: 'POST', body: { dryRun: false } }
23324
- );
23490
+ const syncDecision = buildScopeGrantSourceSyncDecision({
23491
+ source: body,
23492
+ dimensionMap,
23493
+ stagedFormBindingOverlays,
23494
+ });
23495
+ if (syncDecision.defer) {
23496
+ published.sync = {
23497
+ status: 'deferred-until-form-activation',
23498
+ reason: syncDecision.reason,
23499
+ formUuids: syncDecision.formUuids,
23500
+ formCodes: syncDecision.formCodes,
23501
+ resumeTrigger: 'on_write_after_app_finalize',
23502
+ };
23503
+ result.warnings.push(
23504
+ `scopeGrantSource:${code}: 初始同步依赖同一 deployment 的新 staged FormRelease,当前尚无 active 数据表;已保存 on_write 配置,App finalize 后首次表单写入会自动物化`
23505
+ );
23506
+ } else {
23507
+ published.sync = await requestWithAuth(
23508
+ config,
23509
+ target.profileName,
23510
+ `/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/scope/grant-sources/${encodeURIComponent(code)}/sync`,
23511
+ { method: 'POST', body: { dryRun: false } }
23512
+ );
23513
+ }
23325
23514
  }
23326
23515
  result.published.push(published);
23327
23516
  }
23328
23517
  }
23329
23518
 
23519
+ function buildScopeGrantSourceSyncDecision({
23520
+ source,
23521
+ dimensionMap = new Map(),
23522
+ stagedFormBindingOverlays = new Map(),
23523
+ } = {}) {
23524
+ const stagedNewForms = new Map(
23525
+ Array.from(stagedFormBindingOverlays.values())
23526
+ .filter(item => item?.parentReleaseId === null && item?.formUuid)
23527
+ .map(item => [
23528
+ String(item.formUuid).trim(),
23529
+ String(item.formCode || '').trim(),
23530
+ ])
23531
+ );
23532
+ if (stagedNewForms.size === 0) {
23533
+ return {
23534
+ defer: false,
23535
+ reason: null,
23536
+ formUuids: [],
23537
+ formCodes: [],
23538
+ };
23539
+ }
23540
+ const referencedFormUuids = [
23541
+ source?.sourceFormUuid,
23542
+ ...(source?.dimensionMappings || []).map(mapping => {
23543
+ const code = String(
23544
+ mapping?.dimensionCode || mapping?.dimension || ''
23545
+ ).trim();
23546
+ const dimension = dimensionMap.get(code);
23547
+ return dimension?.sourceFormUuid || dimension?.formUuid;
23548
+ }),
23549
+ ]
23550
+ .map(value => String(value || '').trim())
23551
+ .filter(Boolean);
23552
+ const formUuids = unique(
23553
+ referencedFormUuids.filter(formUuid =>
23554
+ stagedNewForms.has(formUuid)
23555
+ )
23556
+ ).sort();
23557
+ return {
23558
+ defer: formUuids.length > 0,
23559
+ reason:
23560
+ formUuids.length > 0
23561
+ ? 'staged_new_form_has_no_active_data_table'
23562
+ : null,
23563
+ formUuids,
23564
+ formCodes: unique(
23565
+ formUuids
23566
+ .map(formUuid => stagedNewForms.get(formUuid))
23567
+ .filter(Boolean)
23568
+ ).sort(),
23569
+ };
23570
+ }
23571
+
23330
23572
  async function publishDataScopePolicyResources(config, target, policies, result, options = {}) {
23331
23573
  for (const item of policies) {
23332
23574
  const code = item.code || item.resourceCode;
@@ -25859,7 +26101,123 @@ function resolveManifestPageId(bound, item) {
25859
26101
 
25860
26102
  function resolveManifestMenuId(bound, menuCode) {
25861
26103
  if (!menuCode) return undefined;
25862
- return bound.resources?.menus?.[menuCode]?.menuId || menuCode;
26104
+ return bound.resources?.menus?.[menuCode]?.menuId;
26105
+ }
26106
+
26107
+ function menuResourceCode(menuItem) {
26108
+ return String(menuItem?.code || menuItem?.resourceCode || '').trim();
26109
+ }
26110
+
26111
+ function menuPlatformId(menuItem) {
26112
+ const value = menuItem?.id || menuItem?.menuId;
26113
+ return value ? String(value) : undefined;
26114
+ }
26115
+
26116
+ function resolveMenuParentId(bound, menuItem, existingMenus = new Map()) {
26117
+ const parentCode = String(menuItem?.parentCode || '').trim();
26118
+ if (!parentCode) return menuItem?.parentId || null;
26119
+ const boundId = resolveManifestMenuId(bound, parentCode);
26120
+ const onlineId = menuPlatformId(existingMenus.get(parentCode));
26121
+ return onlineId || boundId || menuItem?.parentId;
26122
+ }
26123
+
26124
+ function menuParentResolutionError(code, parentCode, label) {
26125
+ const error = new Error(
26126
+ `MENU_PARENT_UNRESOLVED: ${label || `menu ${code}`} 无法将 parentCode ${parentCode} 解析为在线 menuId`
26127
+ );
26128
+ error.code = 'MENU_PARENT_UNRESOLVED';
26129
+ return error;
26130
+ }
26131
+
26132
+ function requireResolvedMenuParentId(
26133
+ bound,
26134
+ menuItem,
26135
+ existingMenus = new Map(),
26136
+ label
26137
+ ) {
26138
+ const parentId = resolveMenuParentId(bound, menuItem, existingMenus);
26139
+ if (menuItem?.parentCode && !parentId) {
26140
+ throw menuParentResolutionError(
26141
+ menuResourceCode(menuItem),
26142
+ menuItem.parentCode,
26143
+ label
26144
+ );
26145
+ }
26146
+ return parentId || null;
26147
+ }
26148
+
26149
+ function orderMenuResourcesForPublish(
26150
+ menus = [],
26151
+ bound = {},
26152
+ existingMenus = new Map()
26153
+ ) {
26154
+ const items = Array.isArray(menus) ? menus : [];
26155
+ const byCode = new Map();
26156
+ const sourceIndex = new Map();
26157
+ for (const [index, menuItem] of items.entries()) {
26158
+ const code = menuResourceCode(menuItem);
26159
+ if (!code) {
26160
+ const error = new Error('MENU_CODE_REQUIRED: menu resource 缺少 code');
26161
+ error.code = 'MENU_CODE_REQUIRED';
26162
+ throw error;
26163
+ }
26164
+ if (byCode.has(code)) {
26165
+ const error = new Error(`MENU_CODE_DUPLICATE: menu code ${code} 重复`);
26166
+ error.code = 'MENU_CODE_DUPLICATE';
26167
+ throw error;
26168
+ }
26169
+ byCode.set(code, menuItem);
26170
+ sourceIndex.set(code, index);
26171
+ }
26172
+
26173
+ const children = new Map();
26174
+ const indegree = new Map([...byCode.keys()].map(code => [code, 0]));
26175
+ for (const [code, menuItem] of byCode) {
26176
+ const parentCode = String(menuItem.parentCode || '').trim();
26177
+ if (!parentCode) continue;
26178
+ if (!byCode.has(parentCode)) {
26179
+ if (!resolveMenuParentId(bound, menuItem, existingMenus)) {
26180
+ throw menuParentResolutionError(code, parentCode, `menu ${code}`);
26181
+ }
26182
+ continue;
26183
+ }
26184
+ indegree.set(code, indegree.get(code) + 1);
26185
+ const siblings = children.get(parentCode) || [];
26186
+ siblings.push(code);
26187
+ children.set(parentCode, siblings);
26188
+ }
26189
+
26190
+ const bySourceOrder = (left, right) =>
26191
+ sourceIndex.get(left) - sourceIndex.get(right);
26192
+ const ready = [...indegree.entries()]
26193
+ .filter(([, count]) => count === 0)
26194
+ .map(([code]) => code)
26195
+ .sort(bySourceOrder);
26196
+ const ordered = [];
26197
+ while (ready.length > 0) {
26198
+ const code = ready.shift();
26199
+ ordered.push(byCode.get(code));
26200
+ for (const childCode of (children.get(code) || []).sort(bySourceOrder)) {
26201
+ const next = indegree.get(childCode) - 1;
26202
+ indegree.set(childCode, next);
26203
+ if (next === 0) {
26204
+ ready.push(childCode);
26205
+ ready.sort(bySourceOrder);
26206
+ }
26207
+ }
26208
+ }
26209
+ if (ordered.length !== items.length) {
26210
+ const cycleCodes = [...indegree.entries()]
26211
+ .filter(([, count]) => count > 0)
26212
+ .map(([code]) => code)
26213
+ .sort(bySourceOrder);
26214
+ const error = new Error(
26215
+ `MENU_PARENT_CYCLE: menu parentCode 存在循环依赖: ${cycleCodes.join(' -> ')}`
26216
+ );
26217
+ error.code = 'MENU_PARENT_CYCLE';
26218
+ throw error;
26219
+ }
26220
+ return ordered;
25863
26221
  }
25864
26222
 
25865
26223
  function resolveAutomationManifestPayload(target, automationItem) {
@@ -26011,12 +26369,16 @@ function unwrapFormSettingPlanValue(value, key) {
26011
26369
  return value;
26012
26370
  }
26013
26371
 
26014
- function menuEquals(bound, desired, existing) {
26372
+ function menuEquals(bound, desired, existing, existingMenus = new Map()) {
26015
26373
  if (!existing) return false;
26016
26374
  const desiredFormUuid = resolveManifestFormUuid(bound, desired);
26017
26375
  const desiredPageId = resolveManifestPageId(bound, desired);
26018
- const desiredParentId =
26019
- resolveManifestMenuId(bound, desired.parentCode) || desired.parentId || null;
26376
+ const desiredParentId = resolveMenuParentId(
26377
+ bound,
26378
+ desired,
26379
+ existingMenus
26380
+ );
26381
+ if (desired.parentCode && !desiredParentId) return false;
26020
26382
  return (
26021
26383
  String(existing.name || '') === String(desired.name || desired.code || '') &&
26022
26384
  String(existing.type || '') === String(desired.type || 'nav') &&
@@ -29263,6 +29625,7 @@ function buildWorkspacePublishEnv(
29263
29625
 
29264
29626
  module.exports = {
29265
29627
  buildResourceManifestSddTargets,
29628
+ buildScopeGrantSourceSyncDecision,
29266
29629
  main,
29267
29630
  satisfiedFormCodesFromContext,
29268
29631
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.229",
3
+ "version": "1.0.231",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {