openxiangda 1.0.191 → 1.0.192

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.
@@ -401,10 +401,34 @@ function withReleaseClientSessionArgs(args = [], flags = {}) {
401
401
  : [...args];
402
402
  }
403
403
 
404
+ function isRecoverablePostActivationDeploymentError(error, deployment) {
405
+ if (
406
+ deployment?.status !== 'deployed' ||
407
+ !String(deployment?.targetAppReleaseId || '').trim()
408
+ ) {
409
+ return false;
410
+ }
411
+ const detail = [
412
+ error?.code,
413
+ error?.message,
414
+ error?.publishLeaseCleanupError?.code,
415
+ error?.publishLeaseCleanupError?.message,
416
+ ]
417
+ .filter(Boolean)
418
+ .join(' ');
419
+ return (
420
+ /(publish[\s_-]*lease|publishLease|发布租约)/i.test(detail) &&
421
+ /(concurr|conflict|cleanup|release[\s_-]*end|并发冲突|清理|释放)/i.test(
422
+ detail
423
+ )
424
+ );
425
+ }
426
+
404
427
  module.exports = {
405
428
  bindEnvironmentTarget,
406
429
  buildCandidateBundle,
407
430
  canonicalJson,
431
+ isRecoverablePostActivationDeploymentError,
408
432
  normalizeEnvironmentKind,
409
433
  normalizeManagedChangeSourceBase,
410
434
  normalizeManagedReleaseSourceRevision,
package/lib/cli.js CHANGED
@@ -130,6 +130,7 @@ const { buildDesignReview, renderDesignReview } = require('./design-review');
130
130
  const {
131
131
  bindEnvironmentTarget,
132
132
  buildCandidateBundle,
133
+ isRecoverablePostActivationDeploymentError,
133
134
  normalizeEnvironmentKind,
134
135
  normalizeManagedChangeSourceBase,
135
136
  normalizeManagedReleaseSourceRevision,
@@ -1300,9 +1301,9 @@ async function waitForPublishLeaseAvailability(config, target, flags = {}) {
1300
1301
  }
1301
1302
  }
1302
1303
 
1303
- function readReleaseRecoveryContext(target, changeId) {
1304
+ function readReleaseRecoveryContext(target, changeId, deploymentId) {
1304
1305
  if (!changeId) return null;
1305
- const execution = readReleaseExecution(changeId);
1306
+ const execution = readReleaseExecution(changeId, deploymentId);
1306
1307
  const context = execution?.releaseContext;
1307
1308
  if (!context || typeof context !== 'object' || Array.isArray(context)) {
1308
1309
  return null;
@@ -2642,6 +2643,33 @@ async function deployApplicationCandidate(
2642
2643
  rememberTargetDeployment(target, detail);
2643
2644
  return { deployment: detail, published };
2644
2645
  } catch (error) {
2646
+ let activatedDeployment = null;
2647
+ try {
2648
+ activatedDeployment = await requestWithAuth(
2649
+ config,
2650
+ target.profileName,
2651
+ applicationDeploymentApiPath(logicalApp.code, deployment.id)
2652
+ );
2653
+ } catch {
2654
+ // Preserve the primary deployment error when the read-only check fails.
2655
+ }
2656
+ if (
2657
+ isRecoverablePostActivationDeploymentError(error, activatedDeployment)
2658
+ ) {
2659
+ rememberTargetDeployment(target, activatedDeployment);
2660
+ warn(
2661
+ `AppRelease 已激活,发布租约清理发生并发冲突;部署 ${deployment.id} 保持 deployed,请执行 release status/end 完成只读回合清理。`
2662
+ );
2663
+ return {
2664
+ deployment: activatedDeployment,
2665
+ published: null,
2666
+ recoveredAfterActivation: true,
2667
+ postActivationError: {
2668
+ code: error?.code || 'PUBLISH_LEASE_CLEANUP_CONFLICT',
2669
+ message: maskText(error?.message || String(error)),
2670
+ },
2671
+ };
2672
+ }
2645
2673
  await markApplicationDeploymentFailed(
2646
2674
  config,
2647
2675
  target,
@@ -2698,6 +2726,35 @@ async function runApplicationEnvironmentReleaseCommand(
2698
2726
  );
2699
2727
  }
2700
2728
 
2729
+ if (subcommand === 'reconcile') {
2730
+ const deploymentId =
2731
+ readStringFlag(flags, 'deployment') ||
2732
+ readStringFlag(flags, 'deployment-id') ||
2733
+ positional[0] ||
2734
+ target.bound.lastDeploymentId;
2735
+ const reason = readStringFlag(flags, 'reason');
2736
+ if (!deploymentId || reason.length < 8) {
2737
+ fail(
2738
+ 'release reconcile 必须提供 --deployment <id> 和至少 8 个字符的 --reason'
2739
+ );
2740
+ }
2741
+ const reconciled = await requestWithAuth(
2742
+ config,
2743
+ target.profileName,
2744
+ applicationDeploymentApiPath(
2745
+ logicalApp.code,
2746
+ deploymentId,
2747
+ 'reconcile'
2748
+ ),
2749
+ {
2750
+ method: 'POST',
2751
+ body: { reason },
2752
+ }
2753
+ );
2754
+ rememberTargetDeployment(target, reconciled);
2755
+ return { deployment: reconciled, reconciled: true };
2756
+ }
2757
+
2701
2758
  const candidateId =
2702
2759
  readStringFlag(flags, 'candidate') ||
2703
2760
  readStringFlag(flags, 'candidate-id') ||
@@ -2831,7 +2888,7 @@ async function release(args) {
2831
2888
  if (wantsSubcommandHelp(subcommand, flags)) {
2832
2889
  print([
2833
2890
  '用法: openxiangda release publish|begin|status|explain|integration-status|renew|end [--change id] [--profile name] [--json]',
2834
- ' openxiangda release candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--confirm-production] [--json]',
2891
+ ' openxiangda release candidate|deploy|reconcile|test|promote|rollback [--candidate id] [--environment target] [--confirm-production] [--json]',
2835
2892
  ' openxiangda release backend-head|backend-list|backend-detail|backend-diff|backend-rollback|backend-abort|backend-retry [releaseId] [--profile name] [--json]',
2836
2893
  ' openxiangda release app-capture|app-head|app-list|app-detail|app-diff|app-post-commit|app-retry|app-prepare|app-verify|app-activate|app-finalize|app-rollback|app-abort [releaseId] [--staged-resources-json <JSON|file>] [--activate-staged-children] [--profile name] [--json]',
2837
2894
  '常用流程:',
@@ -2869,7 +2926,7 @@ async function release(args) {
2869
2926
  flags
2870
2927
  );
2871
2928
  if (
2872
- ['candidate', 'deploy', 'test', 'promote', 'rollback'].includes(
2929
+ ['candidate', 'deploy', 'reconcile', 'test', 'promote', 'rollback'].includes(
2873
2930
  subcommand
2874
2931
  )
2875
2932
  ) {
@@ -3002,7 +3059,11 @@ async function release(args) {
3002
3059
  access: 'reconciliation-read',
3003
3060
  });
3004
3061
  const changeId = positional[0] || readStringFlag(flags, 'change');
3005
- const recovered = readReleaseRecoveryContext(target, changeId);
3062
+ const recovered = readReleaseRecoveryContext(
3063
+ target,
3064
+ changeId,
3065
+ readStringFlag(flags, 'deployment-id')
3066
+ );
3006
3067
  const sourceRevision =
3007
3068
  baseline?.releaseSourceRevision ||
3008
3069
  recovered?.context?.releaseSourceRevision;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.191",
3
+ "version": "1.0.192",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {