openxiangda 1.0.230 → 1.0.232
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.
- package/README.md +27 -2
- package/lib/cli.js +453 -68
- package/lib/delivery-v2-cli.js +216 -0
- package/lib/delivery-v2-executor.js +955 -0
- package/lib/delivery-v2-package.js +681 -0
- package/lib/policy.js +4 -0
- package/lib/workspace-bootstrap.js +1 -0
- package/lib/workspace-init.js +4 -20
- package/openxiangda-skills/SKILL.md +21 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +17 -0
- package/package.json +4 -2
- package/packages/sdk/dist/build/index.cjs.map +1 -1
- package/packages/sdk/dist/build/index.d.mts +1 -0
- package/packages/sdk/dist/build/index.d.ts +1 -0
- package/packages/sdk/dist/build/index.mjs.map +1 -1
- package/policy/openxiangda-policy.json +8 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +6 -5
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +6 -6
- package/templates/openxiangda-react-spa/AGENTS.md +5 -1
- package/templates/openxiangda-react-spa/DELIVERY.md +33 -0
- package/templates/openxiangda-react-spa/app-workspace.config.ts +3 -3
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +4 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +4 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +7 -4
- package/templates/sy-lowcode-app-workspace/DELIVERY.md +33 -0
- package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +3 -3
- package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +1 -0
package/lib/cli.js
CHANGED
|
@@ -166,6 +166,7 @@ const {
|
|
|
166
166
|
withReleaseClientSessionArgs,
|
|
167
167
|
} = require('./application-environments');
|
|
168
168
|
const { startDeveloperCenter } = require('./developer-center');
|
|
169
|
+
const { createDeliveryV2Cli } = require('./delivery-v2-cli');
|
|
169
170
|
const {
|
|
170
171
|
describeOpenApiOperation,
|
|
171
172
|
listOpenApiOperations,
|
|
@@ -219,6 +220,8 @@ let readOnlyHttpGuardLabel = null;
|
|
|
219
220
|
let activePublishRequestContext = null;
|
|
220
221
|
let activePublishLeaseHeartbeat = null;
|
|
221
222
|
let activeCommandScopedPublishLease = null;
|
|
223
|
+
let activeDeliveryV2Context = null;
|
|
224
|
+
let deliveryV2Cli = null;
|
|
222
225
|
const genericGitBasePlanCache = new Map();
|
|
223
226
|
|
|
224
227
|
async function main(argv) {
|
|
@@ -270,6 +273,9 @@ async function mainImpl(argv) {
|
|
|
270
273
|
if (command === 'design') return design(rest);
|
|
271
274
|
if (command === 'sdd') return sdd(rest);
|
|
272
275
|
if (command === 'environment') return environment(rest);
|
|
276
|
+
if (['check', 'deploy', 'status', 'retry', 'rollback'].includes(command)) {
|
|
277
|
+
return getDeliveryV2Cli().run(command, rest);
|
|
278
|
+
}
|
|
273
279
|
if (command === 'release') return release(rest);
|
|
274
280
|
if (command === 'studio') return studio(rest);
|
|
275
281
|
if (command === 'task') return task(rest);
|
|
@@ -321,6 +327,11 @@ Usage:
|
|
|
321
327
|
openxiangda design gates|template|review [--topic code] [--json]
|
|
322
328
|
openxiangda sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive [change] [--change id] [--json]
|
|
323
329
|
openxiangda environment init|attach|bind|swap|policy update|list|status|diff|use [preproduction|production] [--logical-app code] [--environment target] [--json]
|
|
330
|
+
openxiangda check [--environment preproduction|production] [--json]
|
|
331
|
+
openxiangda deploy <preproduction|production> [--package <sha256>] [--profile name] [--json]
|
|
332
|
+
openxiangda status <runId> [--environment preproduction|production] [--json]
|
|
333
|
+
openxiangda retry <runId> [--environment preproduction|production] [--json]
|
|
334
|
+
openxiangda rollback <preproduction|production> --to <appReleaseId> [--json]
|
|
324
335
|
openxiangda release publish|begin|status|integration-status|renew|end [--change id] [--profile name] [--watch] [--json]
|
|
325
336
|
openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--replace-manifest --reason text] [--adopt-online-baseline --adoption-reason text] [--json]
|
|
326
337
|
openxiangda task status --change <id> [--profile name] [--watch] [--json]
|
|
@@ -1451,6 +1462,120 @@ async function runOpenXiangdaInProcess(args, options = {}) {
|
|
|
1451
1462
|
}
|
|
1452
1463
|
}
|
|
1453
1464
|
|
|
1465
|
+
function getDeliveryV2Cli() {
|
|
1466
|
+
if (deliveryV2Cli) return deliveryV2Cli;
|
|
1467
|
+
deliveryV2Cli = createDeliveryV2Cli({
|
|
1468
|
+
clearDeliveryContext: clearDeliveryV2Context,
|
|
1469
|
+
downloadWithAuth,
|
|
1470
|
+
getWorkspaceTarget,
|
|
1471
|
+
installDeliveryContext: installDeliveryV2Context,
|
|
1472
|
+
loadConfig,
|
|
1473
|
+
requestFormWithAuth,
|
|
1474
|
+
requestWithAuth,
|
|
1475
|
+
runOpenXiangdaInProcess,
|
|
1476
|
+
});
|
|
1477
|
+
return deliveryV2Cli;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function deliveryV2SourceRevision(packageDigest) {
|
|
1481
|
+
return {
|
|
1482
|
+
repo: `sha256:${packageDigest}`,
|
|
1483
|
+
repositoryId: `sha256:${packageDigest}`,
|
|
1484
|
+
repoAliases: [`sha256:${packageDigest}`],
|
|
1485
|
+
baseCommit: packageDigest,
|
|
1486
|
+
treeHash: packageDigest,
|
|
1487
|
+
branch: 'delivery-v2',
|
|
1488
|
+
mainBranch: null,
|
|
1489
|
+
remoteName: null,
|
|
1490
|
+
remoteUrlHash: null,
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function installDeliveryV2Context(target, run, targets = {}) {
|
|
1495
|
+
const control = run?.control || {};
|
|
1496
|
+
const publishLeaseId = String(control.publishLeaseId || '').trim();
|
|
1497
|
+
const baselineId = String(control.baselineId || '').trim();
|
|
1498
|
+
const changeId = String(control.changeId || '').trim();
|
|
1499
|
+
const clientSessionId = String(control.clientSessionId || '').trim();
|
|
1500
|
+
const packageDigest = String(run?.packageDigest || '').trim();
|
|
1501
|
+
if (
|
|
1502
|
+
!publishLeaseId ||
|
|
1503
|
+
!baselineId ||
|
|
1504
|
+
!changeId ||
|
|
1505
|
+
!clientSessionId ||
|
|
1506
|
+
!/^[a-f0-9]{64}$/.test(packageDigest)
|
|
1507
|
+
) {
|
|
1508
|
+
fail(
|
|
1509
|
+
'DELIVERY_CONTROL_INVALID: ReleaseRun 缺少 publishLease/baseline/change/session/package 控制信息'
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const sourceRevision = deliveryV2SourceRevision(packageDigest);
|
|
1513
|
+
saveChangeBaseline(target, {
|
|
1514
|
+
baselineId,
|
|
1515
|
+
appType: target.appType,
|
|
1516
|
+
profile: target.profileName,
|
|
1517
|
+
changeId,
|
|
1518
|
+
clientSessionId,
|
|
1519
|
+
sourceBase: {
|
|
1520
|
+
repo: sourceRevision.repo,
|
|
1521
|
+
baseCommit: packageDigest,
|
|
1522
|
+
treeHash: packageDigest,
|
|
1523
|
+
},
|
|
1524
|
+
releaseSourceRevision: sourceRevision,
|
|
1525
|
+
resourceHeads: {},
|
|
1526
|
+
lifecycleState: 'active',
|
|
1527
|
+
});
|
|
1528
|
+
savePublishLease(target, {
|
|
1529
|
+
leaseId: publishLeaseId,
|
|
1530
|
+
expiresAt:
|
|
1531
|
+
control.leaseExpiresAt ||
|
|
1532
|
+
new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
1533
|
+
appType: target.appType,
|
|
1534
|
+
profile: target.profileName,
|
|
1535
|
+
changeId,
|
|
1536
|
+
clientSessionId,
|
|
1537
|
+
baseRevision: packageDigest,
|
|
1538
|
+
holder: 'self',
|
|
1539
|
+
workspace: {
|
|
1540
|
+
branch: 'delivery-v2',
|
|
1541
|
+
commit: packageDigest,
|
|
1542
|
+
treeHash: packageDigest,
|
|
1543
|
+
},
|
|
1544
|
+
});
|
|
1545
|
+
activeDeliveryV2Context = {
|
|
1546
|
+
runId: run.id,
|
|
1547
|
+
appType: target.appType,
|
|
1548
|
+
profileName: target.profileName,
|
|
1549
|
+
packageDigest,
|
|
1550
|
+
publishLeaseId,
|
|
1551
|
+
baselineId,
|
|
1552
|
+
changeId,
|
|
1553
|
+
clientSessionId,
|
|
1554
|
+
leaseExpiresAt: control.leaseExpiresAt,
|
|
1555
|
+
sourceRevision,
|
|
1556
|
+
targets,
|
|
1557
|
+
};
|
|
1558
|
+
activePublishRequestContext = normalizePublishContext({
|
|
1559
|
+
appType: target.appType,
|
|
1560
|
+
profileName: target.profileName,
|
|
1561
|
+
publishLeaseId,
|
|
1562
|
+
changeBaselineId: baselineId,
|
|
1563
|
+
changeId,
|
|
1564
|
+
clientSessionId,
|
|
1565
|
+
});
|
|
1566
|
+
return activeDeliveryV2Context;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function clearDeliveryV2Context(target, run) {
|
|
1570
|
+
const context = activeDeliveryV2Context;
|
|
1571
|
+
if (!context) return;
|
|
1572
|
+
if (run?.id && context.runId !== run.id) return;
|
|
1573
|
+
clearPublishLease(target, context.publishLeaseId);
|
|
1574
|
+
clearChangeBaseline(target, context.baselineId);
|
|
1575
|
+
activePublishRequestContext = null;
|
|
1576
|
+
activeDeliveryV2Context = null;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1454
1579
|
function normalizeReleaseWaitSeconds(flags) {
|
|
1455
1580
|
const raw =
|
|
1456
1581
|
readStringFlag(flags, 'wait-seconds') ||
|
|
@@ -6478,22 +6603,35 @@ function assertStagedAppReleaseScope(target, flags, resources) {
|
|
|
6478
6603
|
}
|
|
6479
6604
|
const stagedContext = assertStagedResourcesContext(target, changeId);
|
|
6480
6605
|
let scope;
|
|
6481
|
-
|
|
6482
|
-
scope =
|
|
6483
|
-
cwd: process.cwd(),
|
|
6484
|
-
configText: readWorkspaceConfigText(),
|
|
6606
|
+
if (activeDeliveryV2Context?.changeId === changeId) {
|
|
6607
|
+
scope = {
|
|
6485
6608
|
changeId,
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6609
|
+
targets: activeDeliveryV2Context.targets || {},
|
|
6610
|
+
changedFiles: [],
|
|
6611
|
+
deliveryRunId: activeDeliveryV2Context.runId,
|
|
6612
|
+
};
|
|
6613
|
+
} else {
|
|
6614
|
+
try {
|
|
6615
|
+
scope = getSddChangeScope({
|
|
6616
|
+
cwd: process.cwd(),
|
|
6617
|
+
configText: readWorkspaceConfigText(),
|
|
6490
6618
|
changeId,
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6619
|
+
});
|
|
6620
|
+
} catch (error) {
|
|
6621
|
+
if (
|
|
6622
|
+
/SDD 未初始化|change 不存在|ENOENT/i.test(
|
|
6623
|
+
String(error?.message || '')
|
|
6624
|
+
)
|
|
6625
|
+
) {
|
|
6626
|
+
return {
|
|
6627
|
+
changeId,
|
|
6628
|
+
scope: null,
|
|
6629
|
+
resources: normalizeStagedAppReleaseResources(resources),
|
|
6630
|
+
compatibility: 'unmanaged_sdd_scope',
|
|
6631
|
+
};
|
|
6632
|
+
}
|
|
6633
|
+
throw error;
|
|
6495
6634
|
}
|
|
6496
|
-
throw error;
|
|
6497
6635
|
}
|
|
6498
6636
|
const runtimeMode = getWorkspaceRuntimeModeFromConfig(
|
|
6499
6637
|
readWorkspaceConfigText()
|
|
@@ -6799,6 +6937,7 @@ function appReleaseEnvironmentControl(flags = {}) {
|
|
|
6799
6937
|
environmentId: readStringFlag(flags, 'environment-id') || undefined,
|
|
6800
6938
|
candidateId: readStringFlag(flags, 'candidate-id') || undefined,
|
|
6801
6939
|
deploymentId: readStringFlag(flags, 'deployment-id') || undefined,
|
|
6940
|
+
deliveryRunId: readStringFlag(flags, 'delivery-run-id') || undefined,
|
|
6802
6941
|
});
|
|
6803
6942
|
}
|
|
6804
6943
|
|
|
@@ -7528,6 +7667,30 @@ async function renewPublishLease(config, target, lease, flags = {}) {
|
|
|
7528
7667
|
}
|
|
7529
7668
|
|
|
7530
7669
|
async function resolvePublishLeaseForWrite(config, target, flags = {}) {
|
|
7670
|
+
if (
|
|
7671
|
+
activeDeliveryV2Context?.appType === target.appType &&
|
|
7672
|
+
activeDeliveryV2Context?.profileName === target.profileName
|
|
7673
|
+
) {
|
|
7674
|
+
activePublishRequestContext = normalizePublishContext({
|
|
7675
|
+
appType: target.appType,
|
|
7676
|
+
profileName: target.profileName,
|
|
7677
|
+
publishLeaseId: activeDeliveryV2Context.publishLeaseId,
|
|
7678
|
+
changeBaselineId: activeDeliveryV2Context.baselineId,
|
|
7679
|
+
changeId: activeDeliveryV2Context.changeId,
|
|
7680
|
+
clientSessionId: activeDeliveryV2Context.clientSessionId,
|
|
7681
|
+
});
|
|
7682
|
+
return {
|
|
7683
|
+
leaseId: activeDeliveryV2Context.publishLeaseId,
|
|
7684
|
+
expiresAt: activeDeliveryV2Context.leaseExpiresAt,
|
|
7685
|
+
appType: target.appType,
|
|
7686
|
+
profile: target.profileName,
|
|
7687
|
+
changeId: activeDeliveryV2Context.changeId,
|
|
7688
|
+
clientSessionId: activeDeliveryV2Context.clientSessionId,
|
|
7689
|
+
baseRevision: activeDeliveryV2Context.packageDigest,
|
|
7690
|
+
holder: 'self',
|
|
7691
|
+
explicit: true,
|
|
7692
|
+
};
|
|
7693
|
+
}
|
|
7531
7694
|
const explicitLeaseId = readStringFlag(flags, 'publish-lease-id');
|
|
7532
7695
|
const changeId = readStringFlag(flags, 'change');
|
|
7533
7696
|
const storedLease = getStoredPublishLease(target);
|
|
@@ -14807,7 +14970,9 @@ async function runtime(args) {
|
|
|
14807
14970
|
// Preserve Runtime's dedicated dirty-source contract before the generic
|
|
14808
14971
|
// frozen-release revision check runs inside lease resolution. The release
|
|
14809
14972
|
// freeze and clean-HEAD gates still run immediately afterwards.
|
|
14810
|
-
|
|
14973
|
+
if (!activeDeliveryV2Context) {
|
|
14974
|
+
assertRuntimeSourceClean(process.cwd());
|
|
14975
|
+
}
|
|
14811
14976
|
// Keep build after lease acquisition. In direct-storage modes the build
|
|
14812
14977
|
// embeds the provider-specific assetBaseUrl returned by the protected
|
|
14813
14978
|
// upload plan; substituting the gateway URL would change chunk/publicPath
|
|
@@ -14819,13 +14984,26 @@ async function runtime(args) {
|
|
|
14819
14984
|
const runtimeHead = await telemetry.runPhase('head', async () =>
|
|
14820
14985
|
loadRuntimeHeadApp(config, target)
|
|
14821
14986
|
);
|
|
14822
|
-
const runtimeLineage =
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14987
|
+
const runtimeLineage = activeDeliveryV2Context
|
|
14988
|
+
? {
|
|
14989
|
+
sourceRevision: activeDeliveryV2Context.sourceRevision,
|
|
14990
|
+
parentReleaseId: runtimeHead?.activeRuntimeReleaseId || null,
|
|
14991
|
+
parentSourceRevision:
|
|
14992
|
+
runtimeHead?.activeRuntimeSourceRevision || null,
|
|
14993
|
+
lineageBypassReason: null,
|
|
14994
|
+
lineageStatus: 'delivery-v2-package',
|
|
14995
|
+
legacyBootstrap: Boolean(
|
|
14996
|
+
runtimeHead?.activeRuntimeReleaseId &&
|
|
14997
|
+
!runtimeHead?.activeRuntimeSourceRevision
|
|
14998
|
+
),
|
|
14999
|
+
}
|
|
15000
|
+
: prepareRuntimeDeployLineage({
|
|
15001
|
+
cwd: process.cwd(),
|
|
15002
|
+
app: runtimeHead,
|
|
15003
|
+
activate: !flags['no-activate'],
|
|
15004
|
+
allowRollback: flags['allow-runtime-rollback'],
|
|
15005
|
+
reason: readStringFlag(flags, 'reason'),
|
|
15006
|
+
});
|
|
14829
15007
|
if (runtimeLineage.lineageStatus === 'rollback-bypass') {
|
|
14830
15008
|
warn(
|
|
14831
15009
|
`[RUNTIME_LINEAGE_BYPASS] 已允许审计式 Runtime 源码回退: reason=${runtimeLineage.lineageBypassReason}`
|
|
@@ -14904,13 +15082,15 @@ async function runtime(args) {
|
|
|
14904
15082
|
jsonOutput: Boolean(flags.json),
|
|
14905
15083
|
})
|
|
14906
15084
|
);
|
|
14907
|
-
const postBuildLineage =
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
15085
|
+
const postBuildLineage = activeDeliveryV2Context
|
|
15086
|
+
? runtimeLineage
|
|
15087
|
+
: prepareRuntimeDeployLineage({
|
|
15088
|
+
cwd: process.cwd(),
|
|
15089
|
+
app: runtimeHead,
|
|
15090
|
+
activate: !flags['no-activate'],
|
|
15091
|
+
allowRollback: flags['allow-runtime-rollback'],
|
|
15092
|
+
reason: readStringFlag(flags, 'reason'),
|
|
15093
|
+
});
|
|
14914
15094
|
assertRuntimeSourceRevisionStable(
|
|
14915
15095
|
runtimeLineage.sourceRevision,
|
|
14916
15096
|
postBuildLineage.sourceRevision
|
|
@@ -16309,7 +16489,12 @@ function removeDirectStateResource(target, resourceType, code) {
|
|
|
16309
16489
|
function normalizeMenuDirectBody(bound, menuItem) {
|
|
16310
16490
|
const formUuid = resolveManifestFormUuid(bound, menuItem);
|
|
16311
16491
|
const pageId = resolveManifestPageId(bound, menuItem);
|
|
16312
|
-
const parentId =
|
|
16492
|
+
const parentId = requireResolvedMenuParentId(
|
|
16493
|
+
bound,
|
|
16494
|
+
menuItem,
|
|
16495
|
+
new Map(),
|
|
16496
|
+
'direct menu mutation'
|
|
16497
|
+
);
|
|
16313
16498
|
return stripUndefinedValues({
|
|
16314
16499
|
resourceCode: menuItem.code || menuItem.resourceCode,
|
|
16315
16500
|
name: menuItem.name || menuItem.code,
|
|
@@ -19087,7 +19272,19 @@ async function buildResourcePlan(
|
|
|
19087
19272
|
]);
|
|
19088
19273
|
const actions = [];
|
|
19089
19274
|
addPlanActions(actions, 'role', manifest.roles, existing.roles, roleEquals);
|
|
19090
|
-
|
|
19275
|
+
const orderedMenus = orderMenuResourcesForPublish(
|
|
19276
|
+
manifest.menus,
|
|
19277
|
+
target.bound,
|
|
19278
|
+
existing.menus
|
|
19279
|
+
);
|
|
19280
|
+
addPlanActions(
|
|
19281
|
+
actions,
|
|
19282
|
+
'menu',
|
|
19283
|
+
orderedMenus,
|
|
19284
|
+
existing.menus,
|
|
19285
|
+
(item, current) =>
|
|
19286
|
+
menuEquals(target.bound, item, current, existing.menus)
|
|
19287
|
+
);
|
|
19091
19288
|
addPlanActions(actions, 'connector', manifest.connectors, existing.connectors, connectorEquals);
|
|
19092
19289
|
addNotificationPlanActions(target, actions, manifest.notifications || [], existing);
|
|
19093
19290
|
await addWorkflowPlanActions(config, target, actions, manifest.workflows, existing.workflows);
|
|
@@ -21410,6 +21607,14 @@ async function runResourceChangeBaselinePreflight(
|
|
|
21410
21607
|
flags = {},
|
|
21411
21608
|
publishLease = null
|
|
21412
21609
|
) {
|
|
21610
|
+
if (activeDeliveryV2Context) {
|
|
21611
|
+
return {
|
|
21612
|
+
ok: true,
|
|
21613
|
+
baselineId: activeDeliveryV2Context.baselineId,
|
|
21614
|
+
checkedTargets: [],
|
|
21615
|
+
skipped: 'trusted-delivery-v2-package',
|
|
21616
|
+
};
|
|
21617
|
+
}
|
|
21413
21618
|
const onlineBaselineAdoption = resolveOnlineBaselineAdoption(
|
|
21414
21619
|
flags,
|
|
21415
21620
|
planActions,
|
|
@@ -22594,21 +22799,61 @@ async function publishFormSettingsResources(
|
|
|
22594
22799
|
}
|
|
22595
22800
|
|
|
22596
22801
|
async function publishMenuResources(config, target, menus, result, options = {}) {
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22802
|
+
if (menus.length === 0) return;
|
|
22803
|
+
const existingMenus = await fetchExistingMenusByCode(config, target);
|
|
22804
|
+
const orderedMenus = orderMenuResourcesForPublish(
|
|
22805
|
+
menus,
|
|
22806
|
+
target.bound,
|
|
22807
|
+
existingMenus
|
|
22808
|
+
);
|
|
22809
|
+
for (const menuItem of orderedMenus) {
|
|
22810
|
+
const code = menuResourceCode(menuItem);
|
|
22811
|
+
const plannedAction = getPlannedResourceAction(options, 'menu', code);
|
|
22812
|
+
const existing = existingMenus.get(code);
|
|
22813
|
+
if (existing && !menuPlatformId(existing)) {
|
|
22814
|
+
const error = new Error(
|
|
22815
|
+
`MENU_ONLINE_ID_REQUIRED: 在线 menu ${code} 缺少 menuId,禁止执行恢复发布`
|
|
22816
|
+
);
|
|
22817
|
+
error.code = 'MENU_ONLINE_ID_REQUIRED';
|
|
22818
|
+
throw error;
|
|
22819
|
+
}
|
|
22820
|
+
if (
|
|
22821
|
+
['noop', 'update'].includes(plannedAction?.action) &&
|
|
22822
|
+
!menuPlatformId(existing)
|
|
22823
|
+
) {
|
|
22824
|
+
const error = new Error(
|
|
22825
|
+
`MENU_PLAN_TARGET_MISSING: menu ${code} 的冻结计划为 ${plannedAction.action},但写入前在线菜单已不存在`
|
|
22826
|
+
);
|
|
22827
|
+
error.code = 'MENU_PLAN_TARGET_MISSING';
|
|
22828
|
+
throw error;
|
|
22829
|
+
}
|
|
22830
|
+
}
|
|
22831
|
+
for (const menuItem of orderedMenus) {
|
|
22832
|
+
const code = menuResourceCode(menuItem);
|
|
22833
|
+
const existing = existingMenus.get(code);
|
|
22834
|
+
if (shouldSkipNoopResource(options, 'menu', code)) {
|
|
22835
|
+
const existingId = menuPlatformId(existing);
|
|
22836
|
+
saveMenuResource(target, code, existingId, {
|
|
22837
|
+
formUuid: existing.formUuid,
|
|
22838
|
+
pageId: existing.pageId,
|
|
22839
|
+
parentId: existing.parentId,
|
|
22840
|
+
routeCode: existing.routeCode,
|
|
22841
|
+
path: existing.path,
|
|
22842
|
+
});
|
|
22843
|
+
recordNoopResource(result, 'menu', code, options);
|
|
22600
22844
|
continue;
|
|
22601
22845
|
}
|
|
22602
|
-
const existing = await findExistingMenu(config, target, menuItem.code);
|
|
22603
22846
|
const formUuid = resolveManifestFormUuid(target.bound, menuItem);
|
|
22604
22847
|
const pageId = resolveManifestPageId(target.bound, menuItem);
|
|
22605
|
-
const parentId =
|
|
22606
|
-
|
|
22607
|
-
menuItem
|
|
22608
|
-
|
|
22848
|
+
const parentId = requireResolvedMenuParentId(
|
|
22849
|
+
target.bound,
|
|
22850
|
+
menuItem,
|
|
22851
|
+
existingMenus,
|
|
22852
|
+
`resource publish menu ${code}`
|
|
22853
|
+
);
|
|
22609
22854
|
const body = {
|
|
22610
|
-
resourceCode:
|
|
22611
|
-
name: menuItem.name ||
|
|
22855
|
+
resourceCode: code,
|
|
22856
|
+
name: menuItem.name || code,
|
|
22612
22857
|
type: menuItem.type || 'nav',
|
|
22613
22858
|
formUuid,
|
|
22614
22859
|
pageId,
|
|
@@ -22619,11 +22864,12 @@ async function publishMenuResources(config, target, menus, result, options = {})
|
|
|
22619
22864
|
routeCode: menuItem.routeCode || null,
|
|
22620
22865
|
path: menuItem.path || null,
|
|
22621
22866
|
};
|
|
22867
|
+
const existingId = menuPlatformId(existing);
|
|
22622
22868
|
let data = existing
|
|
22623
22869
|
? await requestWithAuth(
|
|
22624
22870
|
config,
|
|
22625
22871
|
target.profileName,
|
|
22626
|
-
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(
|
|
22872
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus/${encodeURIComponent(existingId)}`,
|
|
22627
22873
|
{ method: 'POST', body }
|
|
22628
22874
|
)
|
|
22629
22875
|
: await requestWithAuth(
|
|
@@ -22632,37 +22878,54 @@ async function publishMenuResources(config, target, menus, result, options = {})
|
|
|
22632
22878
|
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`,
|
|
22633
22879
|
{ method: 'POST', body }
|
|
22634
22880
|
);
|
|
22635
|
-
if (data?.id) {
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
...(pageId ? { pageId } : {}),
|
|
22642
|
-
...(menuItem.pageCode ? { pageCode: menuItem.pageCode } : {}),
|
|
22643
|
-
...(menuItem.routeCode || data.routeCode
|
|
22644
|
-
? { routeCode: data.routeCode || menuItem.routeCode }
|
|
22645
|
-
: {}),
|
|
22646
|
-
...(menuItem.path || data.path ? { path: data.path || menuItem.path } : {}),
|
|
22647
|
-
});
|
|
22881
|
+
if (!data?.id) {
|
|
22882
|
+
const error = new Error(
|
|
22883
|
+
`MENU_WRITE_ID_REQUIRED: menu ${code} 写入成功响应缺少 menuId,禁止继续发布子菜单`
|
|
22884
|
+
);
|
|
22885
|
+
error.code = 'MENU_WRITE_ID_REQUIRED';
|
|
22886
|
+
throw error;
|
|
22648
22887
|
}
|
|
22649
|
-
|
|
22888
|
+
saveMenuResource(target, code, data.id, {
|
|
22889
|
+
resourceCode: code,
|
|
22890
|
+
name: data.name,
|
|
22891
|
+
type: data.type,
|
|
22892
|
+
...(formUuid ? { formUuid } : {}),
|
|
22893
|
+
...(pageId ? { pageId } : {}),
|
|
22894
|
+
...(menuItem.pageCode ? { pageCode: menuItem.pageCode } : {}),
|
|
22895
|
+
...(menuItem.routeCode || data.routeCode
|
|
22896
|
+
? { routeCode: data.routeCode || menuItem.routeCode }
|
|
22897
|
+
: {}),
|
|
22898
|
+
...(menuItem.path || data.path ? { path: data.path || menuItem.path } : {}),
|
|
22899
|
+
parentId: data.parentId || parentId,
|
|
22900
|
+
});
|
|
22901
|
+
existingMenus.set(code, {
|
|
22902
|
+
...(existing || {}),
|
|
22903
|
+
...body,
|
|
22904
|
+
...data,
|
|
22905
|
+
id: data.id,
|
|
22906
|
+
});
|
|
22907
|
+
result.published.push({
|
|
22908
|
+
kind: 'menu',
|
|
22909
|
+
code,
|
|
22910
|
+
action: existing ? 'update' : 'create',
|
|
22911
|
+
id: data?.id,
|
|
22912
|
+
});
|
|
22650
22913
|
}
|
|
22651
22914
|
}
|
|
22652
22915
|
|
|
22653
|
-
async function
|
|
22916
|
+
async function fetchExistingMenusByCode(config, target) {
|
|
22654
22917
|
const data = await requestWithAuth(
|
|
22655
22918
|
config,
|
|
22656
22919
|
target.profileName,
|
|
22657
22920
|
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/menus`
|
|
22658
22921
|
);
|
|
22659
|
-
const
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
return
|
|
22922
|
+
const menus = new Map();
|
|
22923
|
+
indexByCode(
|
|
22924
|
+
menus,
|
|
22925
|
+
flattenItems(normalizeItems(data)),
|
|
22926
|
+
item => item.resourceCode
|
|
22927
|
+
);
|
|
22928
|
+
return menus;
|
|
22666
22929
|
}
|
|
22667
22930
|
|
|
22668
22931
|
async function publishConnectorResources(config, target, connectors, result, options = {}) {
|
|
@@ -26026,7 +26289,123 @@ function resolveManifestPageId(bound, item) {
|
|
|
26026
26289
|
|
|
26027
26290
|
function resolveManifestMenuId(bound, menuCode) {
|
|
26028
26291
|
if (!menuCode) return undefined;
|
|
26029
|
-
return bound.resources?.menus?.[menuCode]?.menuId
|
|
26292
|
+
return bound.resources?.menus?.[menuCode]?.menuId;
|
|
26293
|
+
}
|
|
26294
|
+
|
|
26295
|
+
function menuResourceCode(menuItem) {
|
|
26296
|
+
return String(menuItem?.code || menuItem?.resourceCode || '').trim();
|
|
26297
|
+
}
|
|
26298
|
+
|
|
26299
|
+
function menuPlatformId(menuItem) {
|
|
26300
|
+
const value = menuItem?.id || menuItem?.menuId;
|
|
26301
|
+
return value ? String(value) : undefined;
|
|
26302
|
+
}
|
|
26303
|
+
|
|
26304
|
+
function resolveMenuParentId(bound, menuItem, existingMenus = new Map()) {
|
|
26305
|
+
const parentCode = String(menuItem?.parentCode || '').trim();
|
|
26306
|
+
if (!parentCode) return menuItem?.parentId || null;
|
|
26307
|
+
const boundId = resolveManifestMenuId(bound, parentCode);
|
|
26308
|
+
const onlineId = menuPlatformId(existingMenus.get(parentCode));
|
|
26309
|
+
return onlineId || boundId || menuItem?.parentId;
|
|
26310
|
+
}
|
|
26311
|
+
|
|
26312
|
+
function menuParentResolutionError(code, parentCode, label) {
|
|
26313
|
+
const error = new Error(
|
|
26314
|
+
`MENU_PARENT_UNRESOLVED: ${label || `menu ${code}`} 无法将 parentCode ${parentCode} 解析为在线 menuId`
|
|
26315
|
+
);
|
|
26316
|
+
error.code = 'MENU_PARENT_UNRESOLVED';
|
|
26317
|
+
return error;
|
|
26318
|
+
}
|
|
26319
|
+
|
|
26320
|
+
function requireResolvedMenuParentId(
|
|
26321
|
+
bound,
|
|
26322
|
+
menuItem,
|
|
26323
|
+
existingMenus = new Map(),
|
|
26324
|
+
label
|
|
26325
|
+
) {
|
|
26326
|
+
const parentId = resolveMenuParentId(bound, menuItem, existingMenus);
|
|
26327
|
+
if (menuItem?.parentCode && !parentId) {
|
|
26328
|
+
throw menuParentResolutionError(
|
|
26329
|
+
menuResourceCode(menuItem),
|
|
26330
|
+
menuItem.parentCode,
|
|
26331
|
+
label
|
|
26332
|
+
);
|
|
26333
|
+
}
|
|
26334
|
+
return parentId || null;
|
|
26335
|
+
}
|
|
26336
|
+
|
|
26337
|
+
function orderMenuResourcesForPublish(
|
|
26338
|
+
menus = [],
|
|
26339
|
+
bound = {},
|
|
26340
|
+
existingMenus = new Map()
|
|
26341
|
+
) {
|
|
26342
|
+
const items = Array.isArray(menus) ? menus : [];
|
|
26343
|
+
const byCode = new Map();
|
|
26344
|
+
const sourceIndex = new Map();
|
|
26345
|
+
for (const [index, menuItem] of items.entries()) {
|
|
26346
|
+
const code = menuResourceCode(menuItem);
|
|
26347
|
+
if (!code) {
|
|
26348
|
+
const error = new Error('MENU_CODE_REQUIRED: menu resource 缺少 code');
|
|
26349
|
+
error.code = 'MENU_CODE_REQUIRED';
|
|
26350
|
+
throw error;
|
|
26351
|
+
}
|
|
26352
|
+
if (byCode.has(code)) {
|
|
26353
|
+
const error = new Error(`MENU_CODE_DUPLICATE: menu code ${code} 重复`);
|
|
26354
|
+
error.code = 'MENU_CODE_DUPLICATE';
|
|
26355
|
+
throw error;
|
|
26356
|
+
}
|
|
26357
|
+
byCode.set(code, menuItem);
|
|
26358
|
+
sourceIndex.set(code, index);
|
|
26359
|
+
}
|
|
26360
|
+
|
|
26361
|
+
const children = new Map();
|
|
26362
|
+
const indegree = new Map([...byCode.keys()].map(code => [code, 0]));
|
|
26363
|
+
for (const [code, menuItem] of byCode) {
|
|
26364
|
+
const parentCode = String(menuItem.parentCode || '').trim();
|
|
26365
|
+
if (!parentCode) continue;
|
|
26366
|
+
if (!byCode.has(parentCode)) {
|
|
26367
|
+
if (!resolveMenuParentId(bound, menuItem, existingMenus)) {
|
|
26368
|
+
throw menuParentResolutionError(code, parentCode, `menu ${code}`);
|
|
26369
|
+
}
|
|
26370
|
+
continue;
|
|
26371
|
+
}
|
|
26372
|
+
indegree.set(code, indegree.get(code) + 1);
|
|
26373
|
+
const siblings = children.get(parentCode) || [];
|
|
26374
|
+
siblings.push(code);
|
|
26375
|
+
children.set(parentCode, siblings);
|
|
26376
|
+
}
|
|
26377
|
+
|
|
26378
|
+
const bySourceOrder = (left, right) =>
|
|
26379
|
+
sourceIndex.get(left) - sourceIndex.get(right);
|
|
26380
|
+
const ready = [...indegree.entries()]
|
|
26381
|
+
.filter(([, count]) => count === 0)
|
|
26382
|
+
.map(([code]) => code)
|
|
26383
|
+
.sort(bySourceOrder);
|
|
26384
|
+
const ordered = [];
|
|
26385
|
+
while (ready.length > 0) {
|
|
26386
|
+
const code = ready.shift();
|
|
26387
|
+
ordered.push(byCode.get(code));
|
|
26388
|
+
for (const childCode of (children.get(code) || []).sort(bySourceOrder)) {
|
|
26389
|
+
const next = indegree.get(childCode) - 1;
|
|
26390
|
+
indegree.set(childCode, next);
|
|
26391
|
+
if (next === 0) {
|
|
26392
|
+
ready.push(childCode);
|
|
26393
|
+
ready.sort(bySourceOrder);
|
|
26394
|
+
}
|
|
26395
|
+
}
|
|
26396
|
+
}
|
|
26397
|
+
if (ordered.length !== items.length) {
|
|
26398
|
+
const cycleCodes = [...indegree.entries()]
|
|
26399
|
+
.filter(([, count]) => count > 0)
|
|
26400
|
+
.map(([code]) => code)
|
|
26401
|
+
.sort(bySourceOrder);
|
|
26402
|
+
const error = new Error(
|
|
26403
|
+
`MENU_PARENT_CYCLE: menu parentCode 存在循环依赖: ${cycleCodes.join(' -> ')}`
|
|
26404
|
+
);
|
|
26405
|
+
error.code = 'MENU_PARENT_CYCLE';
|
|
26406
|
+
throw error;
|
|
26407
|
+
}
|
|
26408
|
+
return ordered;
|
|
26030
26409
|
}
|
|
26031
26410
|
|
|
26032
26411
|
function resolveAutomationManifestPayload(target, automationItem) {
|
|
@@ -26178,12 +26557,16 @@ function unwrapFormSettingPlanValue(value, key) {
|
|
|
26178
26557
|
return value;
|
|
26179
26558
|
}
|
|
26180
26559
|
|
|
26181
|
-
function menuEquals(bound, desired, existing) {
|
|
26560
|
+
function menuEquals(bound, desired, existing, existingMenus = new Map()) {
|
|
26182
26561
|
if (!existing) return false;
|
|
26183
26562
|
const desiredFormUuid = resolveManifestFormUuid(bound, desired);
|
|
26184
26563
|
const desiredPageId = resolveManifestPageId(bound, desired);
|
|
26185
|
-
const desiredParentId =
|
|
26186
|
-
|
|
26564
|
+
const desiredParentId = resolveMenuParentId(
|
|
26565
|
+
bound,
|
|
26566
|
+
desired,
|
|
26567
|
+
existingMenus
|
|
26568
|
+
);
|
|
26569
|
+
if (desired.parentCode && !desiredParentId) return false;
|
|
26187
26570
|
return (
|
|
26188
26571
|
String(existing.name || '') === String(desired.name || desired.code || '') &&
|
|
26189
26572
|
String(existing.type || '') === String(desired.type || 'nav') &&
|
|
@@ -28495,6 +28878,7 @@ function buildExplicitSddReleasePlan(targets, options = {}) {
|
|
|
28495
28878
|
}
|
|
28496
28879
|
|
|
28497
28880
|
function assertSddReleaseGate(plan, flags, command) {
|
|
28881
|
+
if (activeDeliveryV2Context) return;
|
|
28498
28882
|
if (!plan?.sdd || plan.sdd.errors.length === 0) return;
|
|
28499
28883
|
const nonBypassable = new Set([
|
|
28500
28884
|
'sdd-release-direct-form-schema-forbidden',
|
|
@@ -28785,7 +29169,8 @@ function isPublishMutationRequest(apiPath, options = {}) {
|
|
|
28785
29169
|
if (
|
|
28786
29170
|
normalizedPath.includes('/publish-lease/') ||
|
|
28787
29171
|
normalizedPath.endsWith('/publish-lease/acquire') ||
|
|
28788
|
-
normalizedPath.includes('/change-baselines')
|
|
29172
|
+
normalizedPath.includes('/change-baselines') ||
|
|
29173
|
+
normalizedPath.includes('/delivery/')
|
|
28789
29174
|
) {
|
|
28790
29175
|
return false;
|
|
28791
29176
|
}
|