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.
- package/lib/application-environments.js +24 -0
- package/lib/cli.js +66 -5
- package/package.json +1 -1
|
@@ -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(
|
|
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;
|