openxiangda 1.0.242 → 1.0.243
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 +301 -10
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -4028,6 +4028,281 @@ async function readManagedDeployment(
|
|
|
4028
4028
|
return deployment;
|
|
4029
4029
|
}
|
|
4030
4030
|
|
|
4031
|
+
function inspectSucceededPreproductionDeployment(
|
|
4032
|
+
deployment,
|
|
4033
|
+
context,
|
|
4034
|
+
now = Date.now()
|
|
4035
|
+
) {
|
|
4036
|
+
const candidate = context?.candidate || {};
|
|
4037
|
+
const target = context?.target || {};
|
|
4038
|
+
const currentAppReleaseId = String(
|
|
4039
|
+
context?.currentAppReleaseId || ''
|
|
4040
|
+
).trim();
|
|
4041
|
+
const evidence = deployment?.evidenceSummary;
|
|
4042
|
+
const reasons = [];
|
|
4043
|
+
if (deployment?.status !== 'succeeded') {
|
|
4044
|
+
reasons.push('status_not_succeeded');
|
|
4045
|
+
}
|
|
4046
|
+
if (deployment?.kind !== 'deploy') {
|
|
4047
|
+
reasons.push('kind_not_preproduction_deploy');
|
|
4048
|
+
}
|
|
4049
|
+
if (deployment?.candidateId !== candidate.id) {
|
|
4050
|
+
reasons.push('candidate_mismatch');
|
|
4051
|
+
}
|
|
4052
|
+
if (deployment?.targetEnvironmentId !== target.environmentId) {
|
|
4053
|
+
reasons.push('target_environment_mismatch');
|
|
4054
|
+
}
|
|
4055
|
+
if (!currentAppReleaseId) reasons.push('current_app_release_missing');
|
|
4056
|
+
if (deployment?.targetAppReleaseId !== currentAppReleaseId) {
|
|
4057
|
+
reasons.push('active_app_release_mismatch');
|
|
4058
|
+
}
|
|
4059
|
+
if (!deployment?.evidenceHash) reasons.push('evidence_hash_missing');
|
|
4060
|
+
if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence)) {
|
|
4061
|
+
reasons.push('evidence_summary_missing');
|
|
4062
|
+
} else {
|
|
4063
|
+
if (sha256Canonical(evidence) !== deployment.evidenceHash) {
|
|
4064
|
+
reasons.push('evidence_hash_mismatch');
|
|
4065
|
+
}
|
|
4066
|
+
if (evidence.outcome !== 'passed') reasons.push('evidence_not_passed');
|
|
4067
|
+
if (evidence.deploymentId !== deployment.id) {
|
|
4068
|
+
reasons.push('evidence_deployment_mismatch');
|
|
4069
|
+
}
|
|
4070
|
+
if (evidence.candidateId !== candidate.id) {
|
|
4071
|
+
reasons.push('evidence_candidate_mismatch');
|
|
4072
|
+
}
|
|
4073
|
+
if (evidence.candidateHash !== candidate.candidateHash) {
|
|
4074
|
+
reasons.push('evidence_candidate_hash_mismatch');
|
|
4075
|
+
}
|
|
4076
|
+
if ((evidence.testPlanHash || null) !== (candidate.testPlanHash || null)) {
|
|
4077
|
+
reasons.push('evidence_test_plan_hash_mismatch');
|
|
4078
|
+
}
|
|
4079
|
+
if (evidence.environmentId !== target.environmentId) {
|
|
4080
|
+
reasons.push('evidence_environment_mismatch');
|
|
4081
|
+
}
|
|
4082
|
+
if (evidence.environmentKind !== 'preproduction') {
|
|
4083
|
+
reasons.push('evidence_environment_kind_mismatch');
|
|
4084
|
+
}
|
|
4085
|
+
if (evidence.appType !== target.appType) {
|
|
4086
|
+
reasons.push('evidence_app_type_mismatch');
|
|
4087
|
+
}
|
|
4088
|
+
if (evidence.appReleaseId !== deployment.targetAppReleaseId) {
|
|
4089
|
+
reasons.push('evidence_app_release_mismatch');
|
|
4090
|
+
}
|
|
4091
|
+
const validUntil = Date.parse(evidence.validUntil || '');
|
|
4092
|
+
if (!Number.isFinite(validUntil) || validUntil <= now) {
|
|
4093
|
+
reasons.push('evidence_expired');
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
return { accepted: reasons.length === 0, reasons };
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
function selectSucceededPreproductionDeployment(
|
|
4100
|
+
deployments,
|
|
4101
|
+
context,
|
|
4102
|
+
now = Date.now()
|
|
4103
|
+
) {
|
|
4104
|
+
const ordered = [...(deployments || [])].sort((left, right) => {
|
|
4105
|
+
const rightCreated =
|
|
4106
|
+
Date.parse(right?.createdAt || right?.finishedAt || '') || 0;
|
|
4107
|
+
const leftCreated =
|
|
4108
|
+
Date.parse(left?.createdAt || left?.finishedAt || '') || 0;
|
|
4109
|
+
return rightCreated - leftCreated;
|
|
4110
|
+
});
|
|
4111
|
+
for (const deployment of ordered) {
|
|
4112
|
+
const validation = inspectSucceededPreproductionDeployment(
|
|
4113
|
+
deployment,
|
|
4114
|
+
context,
|
|
4115
|
+
now
|
|
4116
|
+
);
|
|
4117
|
+
if (validation.accepted) return deployment;
|
|
4118
|
+
}
|
|
4119
|
+
return null;
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
function managedPreproductionEvidenceLineage(
|
|
4123
|
+
deployment,
|
|
4124
|
+
candidate,
|
|
4125
|
+
target
|
|
4126
|
+
) {
|
|
4127
|
+
return {
|
|
4128
|
+
deploymentId: deployment.id,
|
|
4129
|
+
candidateId: candidate.id,
|
|
4130
|
+
candidateHash: candidate.candidateHash,
|
|
4131
|
+
testPlanHash: candidate.testPlanHash || null,
|
|
4132
|
+
environmentId: target.environmentId,
|
|
4133
|
+
environmentKind: target.environmentKind,
|
|
4134
|
+
appType: target.appType,
|
|
4135
|
+
appReleaseId: deployment.targetAppReleaseId,
|
|
4136
|
+
evidenceHash: deployment.evidenceHash || null,
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
function recoveredPreproductionShipPatch(
|
|
4141
|
+
resolution,
|
|
4142
|
+
candidate,
|
|
4143
|
+
target,
|
|
4144
|
+
recoveredAt = new Date().toISOString()
|
|
4145
|
+
) {
|
|
4146
|
+
const deployment = resolution.deployment;
|
|
4147
|
+
return {
|
|
4148
|
+
status: 'awaiting_production_confirmation',
|
|
4149
|
+
preproductionDeploymentId: deployment.id,
|
|
4150
|
+
preproductionAppReleaseId: deployment.targetAppReleaseId,
|
|
4151
|
+
preproductionStatus: deployment.status,
|
|
4152
|
+
preproductionEvidenceHash: deployment.evidenceHash,
|
|
4153
|
+
preproductionEvidenceLineage: managedPreproductionEvidenceLineage(
|
|
4154
|
+
deployment,
|
|
4155
|
+
candidate,
|
|
4156
|
+
target
|
|
4157
|
+
),
|
|
4158
|
+
preproductionRecovery: {
|
|
4159
|
+
status: 'recovered_from_candidate_history',
|
|
4160
|
+
replacedDeploymentId: resolution.replacedDeploymentId,
|
|
4161
|
+
replacedDeploymentStatus: resolution.replacedDeploymentStatus,
|
|
4162
|
+
selectedDeploymentId: deployment.id,
|
|
4163
|
+
recoveredAt,
|
|
4164
|
+
},
|
|
4165
|
+
failure: null,
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
async function resolvePreproductionDeploymentForAcceptance(
|
|
4170
|
+
config,
|
|
4171
|
+
target,
|
|
4172
|
+
logicalAppCode,
|
|
4173
|
+
originalDeploymentId,
|
|
4174
|
+
candidate
|
|
4175
|
+
) {
|
|
4176
|
+
await assertRemoteCandidateMatches(
|
|
4177
|
+
config,
|
|
4178
|
+
target,
|
|
4179
|
+
logicalAppCode,
|
|
4180
|
+
candidate
|
|
4181
|
+
);
|
|
4182
|
+
const status = await requestWithAuth(
|
|
4183
|
+
config,
|
|
4184
|
+
target.profileName,
|
|
4185
|
+
environmentSetApiPath(logicalAppCode, '/status')
|
|
4186
|
+
);
|
|
4187
|
+
const targetStatus = (status?.environments || []).find(
|
|
4188
|
+
environment =>
|
|
4189
|
+
environment?.id === target.environmentId &&
|
|
4190
|
+
environment?.kind === 'preproduction' &&
|
|
4191
|
+
environment?.appType === target.appType
|
|
4192
|
+
);
|
|
4193
|
+
const currentAppReleaseId = String(
|
|
4194
|
+
targetStatus?.heads?.appReleaseId || ''
|
|
4195
|
+
).trim();
|
|
4196
|
+
if (!targetStatus || !currentAppReleaseId) {
|
|
4197
|
+
fail(
|
|
4198
|
+
`PREPRODUCTION_ACTIVE_HEAD_REQUIRED: 预发环境 ${target.environmentId} 缺少可验证的当前 AppRelease head`
|
|
4199
|
+
);
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
let originalDeployment = null;
|
|
4203
|
+
let originalReadError = null;
|
|
4204
|
+
try {
|
|
4205
|
+
originalDeployment = await readManagedDeployment(
|
|
4206
|
+
config,
|
|
4207
|
+
target,
|
|
4208
|
+
logicalAppCode,
|
|
4209
|
+
originalDeploymentId,
|
|
4210
|
+
{ full: true }
|
|
4211
|
+
);
|
|
4212
|
+
} catch (error) {
|
|
4213
|
+
originalReadError = error;
|
|
4214
|
+
}
|
|
4215
|
+
const context = { candidate, target, currentAppReleaseId };
|
|
4216
|
+
if (originalDeployment?.status === 'deployed') {
|
|
4217
|
+
const identityMatches =
|
|
4218
|
+
originalDeployment.kind === 'deploy' &&
|
|
4219
|
+
originalDeployment.candidateId === candidate.id &&
|
|
4220
|
+
originalDeployment.targetEnvironmentId === target.environmentId &&
|
|
4221
|
+
originalDeployment.targetAppReleaseId === currentAppReleaseId;
|
|
4222
|
+
if (identityMatches) {
|
|
4223
|
+
return {
|
|
4224
|
+
deployment: originalDeployment,
|
|
4225
|
+
recovered: false,
|
|
4226
|
+
currentAppReleaseId,
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
if (originalDeployment?.status === 'succeeded') {
|
|
4231
|
+
const validation = inspectSucceededPreproductionDeployment(
|
|
4232
|
+
originalDeployment,
|
|
4233
|
+
context
|
|
4234
|
+
);
|
|
4235
|
+
if (validation.accepted) {
|
|
4236
|
+
return {
|
|
4237
|
+
deployment: originalDeployment,
|
|
4238
|
+
recovered: false,
|
|
4239
|
+
currentAppReleaseId,
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
const history = await requestWithAuth(
|
|
4245
|
+
config,
|
|
4246
|
+
target.profileName,
|
|
4247
|
+
`${applicationDeploymentApiPath(logicalAppCode)}?limit=200`
|
|
4248
|
+
);
|
|
4249
|
+
const summaries = (Array.isArray(history) ? history : [])
|
|
4250
|
+
.filter(
|
|
4251
|
+
deployment =>
|
|
4252
|
+
deployment?.id !== originalDeploymentId &&
|
|
4253
|
+
deployment?.status === 'succeeded' &&
|
|
4254
|
+
deployment?.kind === 'deploy' &&
|
|
4255
|
+
deployment?.candidateId === candidate.id &&
|
|
4256
|
+
deployment?.targetEnvironmentId === target.environmentId &&
|
|
4257
|
+
deployment?.targetAppReleaseId === currentAppReleaseId
|
|
4258
|
+
)
|
|
4259
|
+
.sort((left, right) => {
|
|
4260
|
+
const rightCreated = Date.parse(right?.createdAt || '') || 0;
|
|
4261
|
+
const leftCreated = Date.parse(left?.createdAt || '') || 0;
|
|
4262
|
+
return rightCreated - leftCreated;
|
|
4263
|
+
});
|
|
4264
|
+
const details = [];
|
|
4265
|
+
for (const summary of summaries) {
|
|
4266
|
+
const detail = await requestWithAuth(
|
|
4267
|
+
config,
|
|
4268
|
+
target.profileName,
|
|
4269
|
+
`${applicationDeploymentApiPath(
|
|
4270
|
+
logicalAppCode,
|
|
4271
|
+
summary.id
|
|
4272
|
+
)}?full=true`
|
|
4273
|
+
);
|
|
4274
|
+
details.push(detail);
|
|
4275
|
+
}
|
|
4276
|
+
const replacement = selectSucceededPreproductionDeployment(
|
|
4277
|
+
details,
|
|
4278
|
+
context
|
|
4279
|
+
);
|
|
4280
|
+
if (!replacement) {
|
|
4281
|
+
const originalStatus = originalDeployment?.status || 'unreadable';
|
|
4282
|
+
const error = new Error(
|
|
4283
|
+
`PREPRODUCTION_NOT_READY_FOR_ACCEPTANCE: 预发 deployment ${originalDeploymentId} 当前状态为 ${originalStatus},且未找到同 candidate、同环境、当前 AppRelease 和有效测试证据完全一致的 succeeded replacement`
|
|
4284
|
+
);
|
|
4285
|
+
error.code = 'PREPRODUCTION_NOT_READY_FOR_ACCEPTANCE';
|
|
4286
|
+
error.data = {
|
|
4287
|
+
originalDeploymentId,
|
|
4288
|
+
originalStatus,
|
|
4289
|
+
originalReadErrorCode: originalReadError?.code || null,
|
|
4290
|
+
candidateId: candidate.id,
|
|
4291
|
+
targetEnvironmentId: target.environmentId,
|
|
4292
|
+
currentAppReleaseId,
|
|
4293
|
+
};
|
|
4294
|
+
throw error;
|
|
4295
|
+
}
|
|
4296
|
+
rememberTargetDeployment(target, replacement);
|
|
4297
|
+
return {
|
|
4298
|
+
deployment: replacement,
|
|
4299
|
+
recovered: true,
|
|
4300
|
+
replacedDeploymentId: originalDeploymentId,
|
|
4301
|
+
replacedDeploymentStatus: originalDeployment?.status || 'unreadable',
|
|
4302
|
+
currentAppReleaseId,
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4031
4306
|
async function completeManagedDeployment(
|
|
4032
4307
|
config,
|
|
4033
4308
|
target,
|
|
@@ -4340,21 +4615,25 @@ async function runApplicationShipCommand(
|
|
|
4340
4615
|
);
|
|
4341
4616
|
const candidate = readCandidate(existingShip.candidateId);
|
|
4342
4617
|
assertLocalCandidate(candidate);
|
|
4343
|
-
|
|
4618
|
+
const preproductionResolution = await resolvePreproductionDeploymentForAcceptance(
|
|
4344
4619
|
config,
|
|
4345
4620
|
preproduction,
|
|
4346
4621
|
logicalApp.code,
|
|
4347
4622
|
existingShip.preproductionDeploymentId,
|
|
4348
|
-
|
|
4623
|
+
candidate
|
|
4349
4624
|
);
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4625
|
+
let preproductionDeployment = preproductionResolution.deployment;
|
|
4626
|
+
if (preproductionResolution.recovered) {
|
|
4627
|
+
writeManagedShip(
|
|
4628
|
+
changeId,
|
|
4629
|
+
recoveredPreproductionShipPatch(
|
|
4630
|
+
preproductionResolution,
|
|
4631
|
+
candidate,
|
|
4632
|
+
preproduction
|
|
4633
|
+
)
|
|
4634
|
+
);
|
|
4635
|
+
warn(
|
|
4636
|
+
`预发验收已从同 candidate 的成功部署恢复: ${preproductionResolution.replacedDeploymentId} -> ${preproductionDeployment.id}`
|
|
4358
4637
|
);
|
|
4359
4638
|
}
|
|
4360
4639
|
|
|
@@ -4427,6 +4706,15 @@ async function runApplicationShipCommand(
|
|
|
4427
4706
|
const promotionStartedAt = new Date().toISOString();
|
|
4428
4707
|
writeManagedShip(changeId, {
|
|
4429
4708
|
status: 'confirmed_for_production',
|
|
4709
|
+
preproductionDeploymentId: preproductionDeployment.id,
|
|
4710
|
+
preproductionAppReleaseId: preproductionDeployment.targetAppReleaseId,
|
|
4711
|
+
preproductionStatus: preproductionDeployment.status,
|
|
4712
|
+
preproductionEvidenceHash: preproductionDeployment.evidenceHash,
|
|
4713
|
+
preproductionEvidenceLineage: managedPreproductionEvidenceLineage(
|
|
4714
|
+
preproductionDeployment,
|
|
4715
|
+
candidate,
|
|
4716
|
+
preproduction
|
|
4717
|
+
),
|
|
4430
4718
|
productionConfirmation: {
|
|
4431
4719
|
status: 'confirmed',
|
|
4432
4720
|
confirmedAt,
|
|
@@ -30146,8 +30434,11 @@ module.exports = {
|
|
|
30146
30434
|
buildScopeGrantSourceSyncDecision,
|
|
30147
30435
|
deploymentEvidencePendingError,
|
|
30148
30436
|
deploymentQueueWaitDecision,
|
|
30437
|
+
inspectSucceededPreproductionDeployment,
|
|
30149
30438
|
main,
|
|
30439
|
+
recoveredPreproductionShipPatch,
|
|
30150
30440
|
resolveReleaseCommandScopedFiles,
|
|
30441
|
+
selectSucceededPreproductionDeployment,
|
|
30151
30442
|
resolveDeliveryV2BackendManifestReplacementIntent,
|
|
30152
30443
|
resolveDataViewSourceFormCodes,
|
|
30153
30444
|
satisfiedFormCodesFromContext,
|