openxiangda-devkit-core 2.71.4 → 2.71.6

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.
@@ -1147,8 +1147,8 @@ export class OpenXiangdaApplicationServices {
1147
1147
  if (diagnostics.length > 0) {
1148
1148
  return this.result("deployment.plan", workspace.context.workspace, undefined, diagnostics);
1149
1149
  }
1150
- const source = await operationStage('source', '核对远端主线与源码', () => publishedDeliverySource(workspace.root));
1151
- const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
1150
+ const source = await operationStage('source', '核对远端主线与源码(网络不可达时保留本地来源)', () => publishedDeliverySource(workspace.root));
1151
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true), undefined, input.appSpecChangeId, 'warning-first');
1152
1152
  if (!lifecycle.readyForTest)
1153
1153
  return this.result('deployment.plan', workspace.context.workspace, { lifecycle: summarizeLifecycle(lifecycle) }, lifecycle.diagnostics);
1154
1154
  const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
@@ -1158,6 +1158,7 @@ export class OpenXiangdaApplicationServices {
1158
1158
  return this.result("deployment.plan", workspace.context.workspace, {
1159
1159
  runtimeCapacity,
1160
1160
  workflowActivation,
1161
+ lifecycle: summarizeLifecycle(lifecycle),
1161
1162
  environment: input.environment,
1162
1163
  appCode: workspace.config.app.code,
1163
1164
  sealed: false,
@@ -1168,7 +1169,7 @@ export class OpenXiangdaApplicationServices {
1168
1169
  backendRoot: workspace.config.backend.root,
1169
1170
  configDigest: sources.config.digest,
1170
1171
  contractDigest: sources.contracts.digest,
1171
- }, workflowActivation);
1172
+ }, [...workflowActivation, ...lifecycle.diagnostics]);
1172
1173
  }
1173
1174
  async deploy(input) {
1174
1175
  const workspace = await this.workspace(input.root);
@@ -1181,14 +1182,18 @@ export class OpenXiangdaApplicationServices {
1181
1182
  this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy;生产环境必须显式复用成功的测试 DeploymentRun"),
1182
1183
  ]);
1183
1184
  }
1184
- const source = await operationStage('source', '核对远端主线与源码', () => publishedDeliverySource(workspace.root));
1185
+ const source = await operationStage('source', '核对远端主线与源码(网络不可达时保留本地来源)', () => publishedDeliverySource(workspace.root));
1185
1186
  const client = await this.client(workspace.root);
1186
1187
  const capabilities = await client.capabilities();
1188
+ let prerequisiteWarnings = [];
1187
1189
  if (capabilities.features?.['application.deployment-prerequisites']?.status === 'available') {
1188
- await operationStage('prerequisites', '检查内核、源码托管与镜像仓库', async () => {
1190
+ const prerequisites = await operationStage('prerequisites', '检查内核、源码托管与镜像仓库(源码异常转为警告)', async () => {
1189
1191
  const backend = backendRuntimeRequired(workspace.config);
1190
- assertDeploymentPrerequisites(await client.deploymentPrerequisites(workspace.config.app.code, backend), workspace.config.app.code, backend);
1192
+ const report = await client.deploymentPrerequisites(workspace.config.app.code, backend);
1193
+ const warnings = assertDeploymentPrerequisites(report, workspace.config.app.code, backend);
1194
+ return { report, warnings };
1191
1195
  });
1196
+ prerequisiteWarnings = prerequisites.warnings;
1192
1197
  }
1193
1198
  const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
1194
1199
  await operationStage('target-preflight', '目标平台只读预检', () => this.validateTargetConfigurationCompatibility(workspace, input.environment, compileLocalConfiguration(workspace.config, this.toolchainVersion)));
@@ -1204,8 +1209,8 @@ export class OpenXiangdaApplicationServices {
1204
1209
  const generated = await this.generate({ root: workspace.root, check: true });
1205
1210
  if (!generated.ok)
1206
1211
  return { ...generated, operation: 'deploy' };
1207
- const lifecycle = await operationStage('development-records', '核对需求、架构与测试验收计划', async () => {
1208
- const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
1212
+ const lifecycle = await operationStage('development-records', '读取需求、架构与测试验收计划(缺口转为警告)', async () => {
1213
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true), undefined, input.appSpecChangeId, 'warning-first');
1209
1214
  return { ...lifecycle, ok: lifecycle.readyForTest };
1210
1215
  });
1211
1216
  if (!lifecycle.readyForTest)
@@ -1253,8 +1258,13 @@ export class OpenXiangdaApplicationServices {
1253
1258
  idempotencyKey: input.idempotencyKey ||
1254
1259
  `deploy:${built.data.package.digest}:${input.environment}${input.deploymentStrategy === 'maintenance-replace' ? ':maintenance-replace' : ''}`,
1255
1260
  ...(input.requestId ? { requestId: input.requestId } : {}),
1261
+ ...(input.approvalLink ? { approvalLink: true } : {}),
1256
1262
  });
1257
- return this.result("deploy", workspace.context.workspace, deployment, workflowActivation, [
1263
+ return this.result("deploy", workspace.context.workspace, deployment, [
1264
+ ...workflowActivation,
1265
+ ...prerequisiteWarnings,
1266
+ ...lifecycle.diagnostics,
1267
+ ], [
1258
1268
  {
1259
1269
  code: "status",
1260
1270
  label: "查看部署状态",
@@ -1447,7 +1457,7 @@ export class OpenXiangdaApplicationServices {
1447
1457
  if (!appVersionId)
1448
1458
  throw new Error("DEPLOYMENT_APP_VERSION_NOT_AVAILABLE");
1449
1459
  const diagnostics = await this.productionPromotionDiagnostics(workspace, client, source, appVersionId);
1450
- if (diagnostics.length > 0) {
1460
+ if (diagnostics.some(item => item.severity === 'error')) {
1451
1461
  return this.result("deploy", workspace.context.workspace, undefined, diagnostics);
1452
1462
  }
1453
1463
  const deployment = await operationStage('submit', '提交生产晋级', () => client.promote({
@@ -1456,7 +1466,7 @@ export class OpenXiangdaApplicationServices {
1456
1466
  environmentKind: environment,
1457
1467
  idempotencyKey: `promote:${source.packageDigest}:${environment}`,
1458
1468
  }));
1459
- return this.ok("deploy", workspace.context.workspace, deployment, [
1469
+ return this.result("deploy", workspace.context.workspace, deployment, diagnostics, [
1460
1470
  { code: "status", label: "查看生产部署状态", command: `openxiangda status ${deployment.id}` },
1461
1471
  ]);
1462
1472
  }
@@ -1476,12 +1486,21 @@ export class OpenXiangdaApplicationServices {
1476
1486
  assertProductionPromotionPreflight(version, {
1477
1487
  appCode: workspace.config.app.code, sourceDeploymentId: source.id, appVersionId, packageDigest: source.packageDigest,
1478
1488
  });
1479
- const testedLifecycle = lifecycleAtCommit(workspace.root, version.source.commit, this.appSpecContractIndex(workspace, true));
1489
+ let testedLifecycle;
1490
+ try {
1491
+ testedLifecycle = lifecycleAtCommit(workspace.root, version.source.commit, this.appSpecContractIndex(workspace, true));
1492
+ }
1493
+ catch (error) {
1494
+ // 生产晋级使用已封存的 packageDigest;当前机器无法读取来源仓库时仅提示,
1495
+ // 不把本地 Git、VPN 或远端仓库可达性误判成制品不可发布。
1496
+ diagnostics.push(this.advisoryDiagnostic(this.diagnostic('APPSPEC_TESTED_SOURCE_UNAVAILABLE', '无法从测试版本来源读取 AppSpec 验收计划;仍可按不可变测试制品晋级', 'appspec', '网络恢复后补齐验收记录;本次以测试 DeploymentRun 和 packageDigest 为准', { message: error instanceof Error ? error.message : String(error) })));
1497
+ testedLifecycle = { changeId: null, acceptanceIds: [], design: { diagnostics: [] } };
1498
+ }
1480
1499
  if (!testedLifecycle.changeId || !testedLifecycle.acceptanceIds.length) {
1481
- diagnostics.push(this.diagnostic('APPSPEC_TESTED_PLAN_REQUIRED', '指定测试版本缺少可追溯的需求与验收计划', 'appspec', '补齐真实记录并发布新的测试候选,再执行验收和生产晋级'));
1500
+ diagnostics.push(this.advisoryDiagnostic(this.diagnostic('APPSPEC_TESTED_PLAN_REQUIRED', '指定测试版本缺少可追溯的需求与验收计划;仍可继续生产晋级,请在后续补齐记录', 'appspec', '补齐真实记录并在下一次发布前复核;本次仅作为发布风险提示')));
1482
1501
  }
1483
1502
  // 原测试提交中的设计基线也必须有效,当前主线的补写不能补签旧版本。
1484
- diagnostics.push(...testedLifecycle.design.diagnostics);
1503
+ diagnostics.push(...this.advisoryDiagnostics(testedLifecycle.design.diagnostics));
1485
1504
  const acceptance = await operationStage('business-acceptance', '核对原测试版本的业务验收报告', async () => {
1486
1505
  const result = verifyBusinessAcceptance(workspace.root, source, testedLifecycle);
1487
1506
  if (result.ok)
@@ -1492,13 +1511,13 @@ export class OpenXiangdaApplicationServices {
1492
1511
  });
1493
1512
  return result;
1494
1513
  });
1495
- diagnostics.push(...acceptance.diagnostics);
1514
+ diagnostics.push(...this.advisoryDiagnostics(acceptance.diagnostics));
1496
1515
  }
1497
1516
  catch (error) {
1498
1517
  return [this.configurationCompatibilityDiagnostic(error)];
1499
1518
  }
1500
1519
  if (workspace.context.workspace.dirty || version.source.dirty) {
1501
- diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "提交并推送当前改动,或在干净主线目录中晋级该测试版本"));
1520
+ diagnostics.push(this.advisoryDiagnostic(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "测试版本或当前工作区存在未提交变更;本次仍复用已封存测试制品晋级", "git", "后续发布前补齐来源快照;当前制品仍按不可变 packageDigest 晋级")));
1502
1521
  }
1503
1522
  const repository = git(workspace.root, ["config", "--get", "remote.origin.url"]);
1504
1523
  const currentRepository = this.repositoryIdentity(repository);
@@ -1506,9 +1525,9 @@ export class OpenXiangdaApplicationServices {
1506
1525
  if (!currentRepository ||
1507
1526
  !sourceRepository ||
1508
1527
  currentRepository !== sourceRepository) {
1509
- diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_REPOSITORY_MISMATCH", "当前工作区仓库与 AppVersion 来源仓库不一致", "git.remote.origin.url", "在构建该 AppVersion 的同一仓库中执行晋级", { current: repository || null, source: version.source.repository }));
1528
+ diagnostics.push(this.advisoryDiagnostic(this.diagnostic("PRODUCTION_SOURCE_REPOSITORY_MISMATCH", "当前工作区仓库与 AppVersion 来源仓库不一致;仓库归属仅作溯源提示", "git.remote.origin.url", "确认 packageDigest 和测试 DeploymentRun 后继续;如需完整溯源再补充仓库信息", { current: repository || null, source: version.source.repository })));
1510
1529
  }
1511
- if (diagnostics.length)
1530
+ if (diagnostics.some(item => item.severity === 'error'))
1512
1531
  return diagnostics;
1513
1532
  try {
1514
1533
  await operationStage('promotion-source', '核对测试制品的主线来源', () => publishedDeliverySource(workspace.root, version.source.commit));
@@ -1516,7 +1535,7 @@ export class OpenXiangdaApplicationServices {
1516
1535
  catch (error) {
1517
1536
  if (!(error instanceof DeliverySourceError))
1518
1537
  throw error;
1519
- diagnostics.push(this.diagnostic(error.code, error.message, 'git', String(error.data.remediation), error.data));
1538
+ diagnostics.push(this.advisoryDiagnostic(this.diagnostic(error.code, error.message, 'git', String(error.data.remediation), error.data)));
1520
1539
  }
1521
1540
  return diagnostics;
1522
1541
  }
@@ -2080,6 +2099,21 @@ export class OpenXiangdaApplicationServices {
2080
2099
  ...(details ? { details } : {}),
2081
2100
  };
2082
2101
  }
2102
+ advisoryDiagnostic(diagnostic) {
2103
+ return {
2104
+ ...diagnostic,
2105
+ severity: diagnostic.severity === 'error' ? 'warning' : diagnostic.severity,
2106
+ retryable: true,
2107
+ details: {
2108
+ ...diagnostic.details,
2109
+ gateClass: 'advisory',
2110
+ affects: ['release-readiness'],
2111
+ },
2112
+ };
2113
+ }
2114
+ advisoryDiagnostics(diagnostics) {
2115
+ return diagnostics.map(diagnostic => this.advisoryDiagnostic(diagnostic));
2116
+ }
2083
2117
  connectedDevelopmentFailure(error, workspaceRoot) {
2084
2118
  const rawCode = String(error?.code ||
2085
2119
  (error instanceof Error ? error.message.split(":", 1)[0] : "") ||