openxiangda 1.0.240 → 1.0.242

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/lib/cli.js CHANGED
@@ -3698,6 +3698,29 @@ function deploymentQueueDetails(error) {
3698
3698
  return value;
3699
3699
  }
3700
3700
 
3701
+ function deploymentQueueWaitDecision(statusInput) {
3702
+ const status = String(statusInput || '').trim().toLowerCase();
3703
+ return {
3704
+ status,
3705
+ wait: status === 'running',
3706
+ evidencePending: status === 'deployed',
3707
+ };
3708
+ }
3709
+
3710
+ function deploymentEvidencePendingError(deployment = {}) {
3711
+ const deploymentId = String(deployment.deploymentId || deployment.id || '').trim();
3712
+ const error = new Error(
3713
+ `APP_DEPLOYMENT_EVIDENCE_PENDING: 目标环境部署 ${deploymentId || '-'} 已完成激活,当前为 deployed,待提交测试证据;请先执行 openxiangda release test --deployment ${deploymentId || '<id>'} --evidence-json <JSON|file>`
3714
+ );
3715
+ error.code = 'APP_DEPLOYMENT_EVIDENCE_PENDING';
3716
+ error.data = {
3717
+ deploymentId: deploymentId || null,
3718
+ status: 'deployed',
3719
+ nextAction: 'release test',
3720
+ };
3721
+ return error;
3722
+ }
3723
+
3701
3724
  async function createApplicationDeploymentWhenAvailable(
3702
3725
  config,
3703
3726
  target,
@@ -3718,9 +3741,17 @@ async function createApplicationDeploymentWhenAvailable(
3718
3741
  );
3719
3742
  } catch (error) {
3720
3743
  const queue = deploymentQueueDetails(error);
3721
- if (!queue || Date.now() >= deadline || waitSeconds === 0) throw error;
3744
+ if (!queue) throw error;
3722
3745
  const deploymentId = String(queue.deploymentId || '').trim();
3723
3746
  if (!deploymentId) throw error;
3747
+ const queuedState = deploymentQueueWaitDecision(queue.status);
3748
+ if (queuedState.evidencePending) {
3749
+ throw deploymentEvidencePendingError({
3750
+ ...queue,
3751
+ deploymentId,
3752
+ });
3753
+ }
3754
+ if (Date.now() >= deadline || waitSeconds === 0) throw error;
3724
3755
  const delaySeconds = Math.min(
3725
3756
  Math.max(Number(queue.retryAfterSeconds || 5), 1),
3726
3757
  30
@@ -3742,7 +3773,14 @@ async function createApplicationDeploymentWhenAvailable(
3742
3773
  } catch {
3743
3774
  break;
3744
3775
  }
3745
- if (!['running', 'deployed'].includes(active?.status)) break;
3776
+ const activeState = deploymentQueueWaitDecision(active?.status);
3777
+ if (activeState.evidencePending) {
3778
+ throw deploymentEvidencePendingError({
3779
+ ...active,
3780
+ deploymentId,
3781
+ });
3782
+ }
3783
+ if (!activeState.wait) break;
3746
3784
  }
3747
3785
  if (Date.now() >= deadline) {
3748
3786
  fail(
@@ -4004,11 +4042,40 @@ async function completeManagedDeployment(
4004
4042
  evidence: deployment.evidenceSummary || evidenceInput || null,
4005
4043
  };
4006
4044
  }
4007
- if (deployment.status !== 'deployed') {
4045
+ if (!['deployed', 'failed'].includes(deployment.status)) {
4008
4046
  fail(
4009
4047
  `DEPLOYMENT_NOT_READY_FOR_COMPLETION: deployment ${deployment.id} 当前状态为 ${deployment.status}`
4010
4048
  );
4011
4049
  }
4050
+ const reconciled = await requestWithAuth(
4051
+ config,
4052
+ target.profileName,
4053
+ applicationDeploymentApiPath(
4054
+ logicalAppCode,
4055
+ deployment.id,
4056
+ 'reconcile'
4057
+ ),
4058
+ {
4059
+ method: 'POST',
4060
+ body: {
4061
+ reason: 'bind final active AppRelease lineage before test evidence',
4062
+ },
4063
+ }
4064
+ );
4065
+ if (reconciled.status === 'succeeded') {
4066
+ rememberTargetDeployment(target, reconciled);
4067
+ return {
4068
+ deployment: reconciled,
4069
+ evidence: reconciled.evidenceSummary || evidenceInput || null,
4070
+ };
4071
+ }
4072
+ if (reconciled.status !== 'deployed') {
4073
+ fail(
4074
+ `DEPLOYMENT_RECONCILIATION_NOT_CONFIRMED: deployment ${deployment.id} 恢复后状态为 ${reconciled.status}`
4075
+ );
4076
+ }
4077
+ deployment = reconciled;
4078
+ rememberTargetDeployment(target, deployment);
4012
4079
  const evidence = buildBoundDeploymentEvidence(
4013
4080
  evidenceInput,
4014
4081
  target,
@@ -4627,26 +4694,14 @@ async function runApplicationEnvironmentReleaseCommand(
4627
4694
  const candidate = deployment.candidateId
4628
4695
  ? readCandidate(deployment.candidateId)
4629
4696
  : null;
4630
- const evidence = buildBoundDeploymentEvidence(
4631
- readJsonInput(flags['evidence-json'], 'evidence-json'),
4697
+ return await completeManagedDeployment(
4698
+ config,
4632
4699
  target,
4700
+ logicalApp.code,
4633
4701
  deployment,
4634
- candidate
4635
- );
4636
- const completed = await requestWithAuth(
4637
- config,
4638
- target.profileName,
4639
- applicationDeploymentApiPath(logicalApp.code, deploymentId, 'complete'),
4640
- {
4641
- method: 'POST',
4642
- body: {
4643
- evidenceSummary: evidence,
4644
- evidenceHash: sha256Canonical(evidence),
4645
- },
4646
- }
4702
+ candidate,
4703
+ readJsonInput(flags['evidence-json'], 'evidence-json')
4647
4704
  );
4648
- rememberTargetDeployment(target, completed);
4649
- return { deployment: completed, evidence };
4650
4705
  }
4651
4706
 
4652
4707
  if (subcommand === 'rollback') {
@@ -14524,6 +14579,20 @@ async function resource(args) {
14524
14579
  const publishLease = await telemetry.runPhase('lease', async () =>
14525
14580
  resolvePublishLeaseForWrite(config, target, flags)
14526
14581
  );
14582
+ const deliveryV2BackendManifestIntent =
14583
+ resolveDeliveryV2BackendManifestReplacementIntent({
14584
+ flags,
14585
+ typeFilters,
14586
+ codeFilters,
14587
+ manifest,
14588
+ });
14589
+ const publishLeaseFlags = deliveryV2BackendManifestIntent
14590
+ ? {
14591
+ ...flags,
14592
+ reason: deliveryV2BackendManifestIntent.reason,
14593
+ 'replace-reason': deliveryV2BackendManifestIntent.reason,
14594
+ }
14595
+ : flags;
14527
14596
  const changeBaselinePreflight = await telemetry.runPhase(
14528
14597
  'preflight',
14529
14598
  {
@@ -14553,10 +14622,12 @@ async function resource(args) {
14553
14622
  pruneCandidates,
14554
14623
  publishLeaseId: publishLease?.leaseId,
14555
14624
  publishLease,
14556
- publishLeaseFlags: flags,
14625
+ publishLeaseFlags,
14557
14626
  scopedFiles,
14558
14627
  baseRef: flags.since || 'HEAD',
14559
- replaceManifest: Boolean(flags['replace-manifest']),
14628
+ replaceManifest:
14629
+ Boolean(flags['replace-manifest']) ||
14630
+ Boolean(deliveryV2BackendManifestIntent),
14560
14631
  changeId:
14561
14632
  flags.change || getStoredChangeBaseline(target)?.changeId || null,
14562
14633
  stagedFormBindingOverlays,
@@ -14572,6 +14643,10 @@ async function resource(args) {
14572
14643
  Boolean(typeFilters?.has('workflows')),
14573
14644
  })
14574
14645
  );
14646
+ if (deliveryV2BackendManifestIntent) {
14647
+ result.deliveryV2BackendManifestReplacement =
14648
+ deliveryV2BackendManifestIntent;
14649
+ }
14575
14650
  if (changeBaselinePreflight) {
14576
14651
  result.changeBaselinePreflight = changeBaselinePreflight;
14577
14652
  }
@@ -28265,6 +28340,86 @@ function shouldReplaceResourceManifestItem(item, resourceKey, code, options = {}
28265
28340
  return options.replaceManifest === true;
28266
28341
  }
28267
28342
 
28343
+ function resolveDeliveryV2BackendManifestReplacementIntent({
28344
+ flags = {},
28345
+ typeFilters = null,
28346
+ codeFilters = null,
28347
+ manifest = {},
28348
+ deliveryContext = activeDeliveryV2Context,
28349
+ } = {}) {
28350
+ if (!deliveryContext || !flags['stage-only']) return null;
28351
+ const selectedTypes = Array.from(typeFilters || []);
28352
+ if (
28353
+ selectedTypes.length === 0 ||
28354
+ selectedTypes.some(
28355
+ type => !['functions', 'automations'].includes(type)
28356
+ )
28357
+ ) {
28358
+ return null;
28359
+ }
28360
+ if (!Array.isArray(codeFilters) || codeFilters.length === 0) {
28361
+ const error = new Error(
28362
+ 'DELIVERY_BACKEND_SCOPE_MISMATCH: Delivery V2 Backend stage 缺少冻结的精确 selector'
28363
+ );
28364
+ error.code = 'DELIVERY_BACKEND_SCOPE_MISMATCH';
28365
+ error.retryable = false;
28366
+ throw error;
28367
+ }
28368
+ const expectedSelectors = unique([
28369
+ ...(deliveryContext.targets?.functions || []).map(
28370
+ code => `Function:${String(code)}`
28371
+ ),
28372
+ ...(deliveryContext.targets?.automations || []).map(
28373
+ code => `Automation:${String(code)}`
28374
+ ),
28375
+ ]).sort();
28376
+ const actualSelectors = unique([
28377
+ ...(manifest.functions || []).map(
28378
+ item => `Function:${getResourceItemCode(item)}`
28379
+ ),
28380
+ ...(manifest.automations || []).map(
28381
+ item => `Automation:${getResourceItemCode(item)}`
28382
+ ),
28383
+ ])
28384
+ .filter(selector => !selector.endsWith(':'))
28385
+ .sort();
28386
+ if (
28387
+ expectedSelectors.length === 0 ||
28388
+ JSON.stringify(actualSelectors) !== JSON.stringify(expectedSelectors)
28389
+ ) {
28390
+ const error = new Error(
28391
+ `DELIVERY_BACKEND_SCOPE_MISMATCH: Delivery V2 Backend stage 与 ReleaseRun 冻结范围不一致;expected=[${expectedSelectors.join(
28392
+ ','
28393
+ )}] actual=[${actualSelectors.join(',')}]`
28394
+ );
28395
+ error.code = 'DELIVERY_BACKEND_SCOPE_MISMATCH';
28396
+ error.retryable = false;
28397
+ error.expectedSelectors = expectedSelectors;
28398
+ error.actualSelectors = actualSelectors;
28399
+ throw error;
28400
+ }
28401
+ const runId = String(deliveryContext.runId || '').trim();
28402
+ const packageDigest = String(deliveryContext.packageDigest || '').trim();
28403
+ if (!runId || !/^[a-f0-9]{64}$/.test(packageDigest)) {
28404
+ const error = new Error(
28405
+ 'DELIVERY_CONTROL_INVALID: Delivery V2 Backend stage 缺少 ReleaseRun/packageDigest 权威上下文'
28406
+ );
28407
+ error.code = 'DELIVERY_CONTROL_INVALID';
28408
+ error.retryable = false;
28409
+ throw error;
28410
+ }
28411
+ return {
28412
+ mode: 'manifest_replacement',
28413
+ authority: 'delivery_v2_sealed_package',
28414
+ runId,
28415
+ packageDigest,
28416
+ selectors: actualSelectors,
28417
+ reason:
28418
+ `Delivery V2 ReleaseRun ${runId} sealed package ${packageDigest} ` +
28419
+ 'authoritative backend manifest and binding contract replacement',
28420
+ };
28421
+ }
28422
+
28268
28423
  function buildAutomationSourcePatchSources(existingDefinition, desiredDefinition) {
28269
28424
  const existing = existingDefinition || {};
28270
28425
  const desired = desiredDefinition || {};
@@ -29989,8 +30144,11 @@ function buildWorkspacePublishEnv(
29989
30144
  module.exports = {
29990
30145
  buildResourceManifestSddTargets,
29991
30146
  buildScopeGrantSourceSyncDecision,
30147
+ deploymentEvidencePendingError,
30148
+ deploymentQueueWaitDecision,
29992
30149
  main,
29993
30150
  resolveReleaseCommandScopedFiles,
30151
+ resolveDeliveryV2BackendManifestReplacementIntent,
29994
30152
  resolveDataViewSourceFormCodes,
29995
30153
  satisfiedFormCodesFromContext,
29996
30154
  stagedDataViewFormReleaseDependencies,
@@ -882,6 +882,10 @@ function compactStepResult(stepId, result, context = {}) {
882
882
  if (bindingContracts) {
883
883
  compact.backendBindingContracts = bindingContracts;
884
884
  }
885
+ if (result.deliveryV2BackendManifestReplacement) {
886
+ compact.backendManifestReplacement =
887
+ result.deliveryV2BackendManifestReplacement;
888
+ }
885
889
  }
886
890
  return compact;
887
891
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.240",
3
+ "version": "1.0.242",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {