openxiangda-devkit-core 2.0.0-alpha.115 → 2.0.0-alpha.118

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.
Files changed (72) hide show
  1. package/dist/app-spec.d.ts +23 -11
  2. package/dist/app-spec.d.ts.map +1 -1
  3. package/dist/app-spec.js +171 -52
  4. package/dist/app-spec.js.map +1 -1
  5. package/dist/application-services.d.ts +290 -23
  6. package/dist/application-services.d.ts.map +1 -1
  7. package/dist/application-services.js +174 -79
  8. package/dist/application-services.js.map +1 -1
  9. package/dist/backend-image-build.js +6 -11
  10. package/dist/backend-image-build.js.map +1 -1
  11. package/dist/command-process.d.ts +14 -0
  12. package/dist/command-process.d.ts.map +1 -0
  13. package/dist/command-process.js +51 -0
  14. package/dist/command-process.js.map +1 -0
  15. package/dist/command-registry.d.ts +2 -2
  16. package/dist/command-registry.js +1 -1
  17. package/dist/command-registry.js.map +1 -1
  18. package/dist/compiler/ai-catalog.d.ts.map +1 -1
  19. package/dist/compiler/ai-catalog.js +2 -1
  20. package/dist/compiler/ai-catalog.js.map +1 -1
  21. package/dist/compiler/bundle.d.ts.map +1 -1
  22. package/dist/compiler/bundle.js +2 -1
  23. package/dist/compiler/bundle.js.map +1 -1
  24. package/dist/compiler/config.d.ts.map +1 -1
  25. package/dist/compiler/config.js +14 -7
  26. package/dist/compiler/config.js.map +1 -1
  27. package/dist/compiler/permission-review.d.ts.map +1 -1
  28. package/dist/configuration-preflight.d.ts +7 -0
  29. package/dist/configuration-preflight.d.ts.map +1 -0
  30. package/dist/configuration-preflight.js +13 -0
  31. package/dist/configuration-preflight.js.map +1 -0
  32. package/dist/control-plane-client.d.ts +8 -1
  33. package/dist/control-plane-client.d.ts.map +1 -1
  34. package/dist/control-plane-client.js +13 -0
  35. package/dist/control-plane-client.js.map +1 -1
  36. package/dist/delivery-cache.d.ts +34 -0
  37. package/dist/delivery-cache.d.ts.map +1 -0
  38. package/dist/delivery-cache.js +186 -0
  39. package/dist/delivery-cache.js.map +1 -0
  40. package/dist/delivery-source.d.ts +17 -0
  41. package/dist/delivery-source.d.ts.map +1 -0
  42. package/dist/delivery-source.js +85 -0
  43. package/dist/delivery-source.js.map +1 -0
  44. package/dist/deployment-observer.d.ts +14 -0
  45. package/dist/deployment-observer.d.ts.map +1 -0
  46. package/dist/deployment-observer.js +68 -0
  47. package/dist/deployment-observer.js.map +1 -0
  48. package/dist/deployment.d.ts +13 -0
  49. package/dist/deployment.d.ts.map +1 -1
  50. package/dist/deployment.js +34 -7
  51. package/dist/deployment.js.map +1 -1
  52. package/dist/developer-operations.d.ts +147 -43
  53. package/dist/developer-operations.d.ts.map +1 -1
  54. package/dist/developer-operations.js +22 -3
  55. package/dist/developer-operations.js.map +1 -1
  56. package/dist/development-lifecycle.d.ts +48 -0
  57. package/dist/development-lifecycle.d.ts.map +1 -0
  58. package/dist/development-lifecycle.js +180 -0
  59. package/dist/development-lifecycle.js.map +1 -0
  60. package/dist/documentation.d.ts +4 -4
  61. package/dist/documentation.d.ts.map +1 -1
  62. package/dist/documentation.js +4 -5
  63. package/dist/documentation.js.map +1 -1
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/operation-progress.d.ts +26 -0
  69. package/dist/operation-progress.d.ts.map +1 -0
  70. package/dist/operation-progress.js +92 -0
  71. package/dist/operation-progress.js.map +1 -0
  72. package/package.json +2 -2
@@ -1,5 +1,11 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import { randomUUID } from "node:crypto";
2
+ import { publishedDeliverySource, assertDeliverySourceUnchanged, DeliverySourceError } from './delivery-source.js';
3
+ import { operationStage, skippedOperationStage, updateOperationStage } from './operation-progress.js';
4
+ import { deliveryInputDigest, reusableValidation, recordValidation, recordSealedCandidate, reusableSealedCandidate } from './delivery-cache.js';
5
+ import { developmentLifecycle, summarizeLifecycle, lifecycleAtCommit, verifyBusinessAcceptance } from './development-lifecycle.js';
6
+ import { runCommandProcess } from './command-process.js';
7
+ import { compileLocalConfiguration } from './configuration-preflight.js';
8
+ import { NativeConfigurationCompilerError, NATIVE_CONFIGURATION_VALIDATOR_DIGEST } from 'openxiangda-contracts/native-compiler';
3
9
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from "node:fs";
4
10
  import { dirname, join, relative, resolve } from "node:path";
5
11
  import { CONFIGURATION_COMPATIBILITY_CAPABILITY, CURRENT_APPLICATION_CONTRACT, OPENXIANGDA_CONTRACT_VERSION, SCHEMA_VERSIONS, canonicalJson, sha256Digest, } from "openxiangda-contracts";
@@ -24,7 +30,15 @@ export class OpenXiangdaApplicationServices {
24
30
  }
25
31
  async workspaceContext(root) {
26
32
  const workspace = await this.workspace(root);
27
- return this.ok("workspace.context", workspace.context.workspace, workspace.context);
33
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
34
+ return this.ok("workspace.context", workspace.context.workspace, { ...workspace.context, development: {
35
+ ...summarizeLifecycle(lifecycle),
36
+ application: lifecycle.context.application ? { path: lifecycle.context.application.path, content: lifecycle.context.application.content } : null,
37
+ records: [
38
+ ...lifecycle.context.index.capabilities, ...lifecycle.context.index.activeChanges,
39
+ ...lifecycle.context.index.decisions, ...lifecycle.context.index.history,
40
+ ].map(({ id, title, status, path, kind }) => ({ id, title, status, path, kind })),
41
+ } });
28
42
  }
29
43
  async appSpecInit(root) {
30
44
  const workspace = await this.workspace(root);
@@ -77,15 +91,16 @@ export class OpenXiangdaApplicationServices {
77
91
  },
78
92
  ]);
79
93
  }
80
- async appSpecContext(root, selector) {
94
+ async appSpecContext(root, selector, historyOffset = 0) {
81
95
  const workspace = await this.workspace(root);
82
- const context = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, true), selector);
83
- return this.result("spec.context", workspace.context.workspace, context, advisoryAppSpecDiagnostics(context.diagnostics));
96
+ const context = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, true), selector, historyOffset);
97
+ return this.result("spec.context", workspace.context.workspace, { ...context, lifecycle: summarizeLifecycle(developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true))) }, advisoryAppSpecDiagnostics(context.diagnostics));
84
98
  }
85
99
  async appSpecCheck(root) {
86
100
  const workspace = await this.workspace(root);
87
101
  const context = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, true));
88
- return this.result("spec.check", workspace.context.workspace, context, context.diagnostics, [
102
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
103
+ return this.result("spec.check", workspace.context.workspace, { ...context, lifecycle: summarizeLifecycle(lifecycle) }, lifecycle.diagnostics, [
89
104
  {
90
105
  code: "check",
91
106
  label: "继续运行应用检查",
@@ -93,6 +108,13 @@ export class OpenXiangdaApplicationServices {
93
108
  },
94
109
  ]);
95
110
  }
111
+ async appSpecVerify(root, deploymentId, evidencePath) {
112
+ const workspace = await this.workspace(root);
113
+ const run = await (await this.client(workspace.root)).deployment(workspace.config.app.code, deploymentId);
114
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
115
+ const verification = verifyBusinessAcceptance(workspace.root, run, lifecycle, evidencePath);
116
+ return this.result('spec.verify', workspace.context.workspace, verification, verification.diagnostics);
117
+ }
96
118
  async appSpecClose(input) {
97
119
  const workspace = await this.workspace(input.root);
98
120
  const outcome = closeAppSpecChange({
@@ -575,12 +597,14 @@ export class OpenXiangdaApplicationServices {
575
597
  async check(root, environmentKey) {
576
598
  const workspace = await this.workspace(root);
577
599
  const appSpec = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, false));
600
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, false));
578
601
  const diagnostics = [
579
602
  ...validateAppConfig(workspace.config),
580
603
  ...this.versionTrainDiagnostics(workspace),
581
604
  ...validateNestInjectionContract(workspace.root),
582
605
  ...validateApplicationUiContract(workspace.root, workspace.config.frontend.root),
583
606
  ...advisoryAppSpecDiagnostics(appSpec.diagnostics),
607
+ ...advisoryAppSpecDiagnostics(lifecycle.diagnostics),
584
608
  ];
585
609
  if (diagnostics.some(item => item.severity === "error")) {
586
610
  const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
@@ -589,27 +613,28 @@ export class OpenXiangdaApplicationServices {
589
613
  compatibility: null,
590
614
  stages: [],
591
615
  sealedArtifact,
592
- appSpec: summarizeAppSpecContext(appSpec),
616
+ appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
593
617
  }, diagnostics);
594
618
  }
595
619
  let compatibility = null;
596
- if (environmentKey) {
597
- try {
598
- compatibility = await this.validateTargetConfigurationCompatibility(workspace, environmentKey);
599
- }
600
- catch (error) {
601
- diagnostics.push(this.configurationCompatibilityDiagnostic(error));
602
- const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
603
- return this.result("check", workspace.context.workspace, {
604
- generated: undefined,
605
- compatibility: null,
606
- stages: [],
607
- sealedArtifact,
608
- appSpec: summarizeAppSpecContext(appSpec),
609
- }, diagnostics);
620
+ try {
621
+ const local = await operationStage('local-configuration', '本地完整配置校验', async () => compileLocalConfiguration(workspace.config, this.toolchainVersion));
622
+ if (environmentKey) {
623
+ compatibility = await operationStage('target-preflight', '目标平台只读预检', () => this.validateTargetConfigurationCompatibility(workspace, environmentKey, local));
610
624
  }
611
625
  }
612
- const generated = await this.generate({ root: workspace.root });
626
+ catch (error) {
627
+ diagnostics.push(this.configurationCompatibilityDiagnostic(error));
628
+ const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
629
+ return this.result("check", workspace.context.workspace, {
630
+ generated: undefined,
631
+ compatibility: null,
632
+ stages: [],
633
+ sealedArtifact,
634
+ appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
635
+ }, diagnostics);
636
+ }
637
+ const generated = await operationStage('generate', '生成应用契约', () => this.generate({ root: workspace.root }));
613
638
  diagnostics.push(...generated.diagnostics);
614
639
  if (!generated.ok) {
615
640
  const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
@@ -618,7 +643,7 @@ export class OpenXiangdaApplicationServices {
618
643
  compatibility: null,
619
644
  stages: [],
620
645
  sealedArtifact,
621
- appSpec: summarizeAppSpecContext(appSpec),
646
+ appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
622
647
  }, diagnostics, generated.nextActions);
623
648
  }
624
649
  const backendDiagnostics = this.initializeBackend(workspace);
@@ -626,18 +651,37 @@ export class OpenXiangdaApplicationServices {
626
651
  return this.result("check", workspace.context.workspace, undefined, backendDiagnostics);
627
652
  }
628
653
  const stages = [];
654
+ const cache = this.deliveryCacheContext(workspace);
655
+ const inputDigest = deliveryInputDigest(cache);
656
+ const reusable = reusableValidation(cache, inputDigest);
629
657
  for (const name of ["check", "test", "build"]) {
658
+ if (reusable) {
659
+ await operationStage(name, `${this.packageScriptLabel(name)}(复用已验证结果)`, async () => {
660
+ updateOperationStage(name, `${this.packageScriptLabel(name)}(复用已验证结果)`, { reused: true, inputDigest, outputDigest: reusable.outputDigest });
661
+ });
662
+ stages.push({ name, state: 'passed', ok: true, exitCode: 0, output: '输入与构建输出摘要未变,复用已通过的本地检查', reused: true });
663
+ continue;
664
+ }
630
665
  if (stages.some(stage => stage.state === 'failed')) {
631
666
  stages.push({ name, state: 'skipped', ok: false, exitCode: null, output: '前置阶段失败,未执行' });
667
+ skippedOperationStage(name, this.packageScriptLabel(name));
632
668
  continue;
633
669
  }
634
- const result = this.runPackageScript(workspace.root, name);
670
+ const result = await this.runPackageScript(workspace.root, name);
635
671
  stages.push({ name, ...result, state: result.ok ? 'passed' : 'failed' });
636
672
  }
637
673
  for (const stage of stages.filter(item => item.state === 'failed')) {
638
674
  const code = `WORKSPACE_${stage.name.toUpperCase()}_FAILED`;
639
675
  diagnostics.push(this.diagnostic(code, `工作区 ${stage.name} 脚本失败`, `package.json#scripts.${stage.name}`, "修复失败后重新运行 openxiangda check", { output: stage.output }));
640
676
  }
677
+ if (!reusable && stages.every(stage => stage.state === 'passed')) {
678
+ try {
679
+ recordValidation(cache, inputDigest);
680
+ }
681
+ catch (error) {
682
+ diagnostics.push(this.diagnostic('OPENXIANGDA_VALIDATION_INPUT_CHANGED', error.message, 'workspace', '等待源码修改结束后重新运行 openxiangda check'));
683
+ }
684
+ }
641
685
  const passed = !diagnostics.some(item => item.severity === "error");
642
686
  const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, passed);
643
687
  return this.result("check", workspace.context.workspace, {
@@ -645,7 +689,7 @@ export class OpenXiangdaApplicationServices {
645
689
  compatibility,
646
690
  stages,
647
691
  sealedArtifact,
648
- appSpec: summarizeAppSpecContext(appSpec),
692
+ appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
649
693
  }, diagnostics, passed
650
694
  ? [
651
695
  {
@@ -662,7 +706,7 @@ export class OpenXiangdaApplicationServices {
662
706
  if (diagnostics.length > 0) {
663
707
  return this.result("test", workspace.context.workspace, undefined, diagnostics);
664
708
  }
665
- const command = this.runPackageScript(workspace.root, "test");
709
+ const command = await this.runPackageScript(workspace.root, "test");
666
710
  const commandDiagnostics = command.ok
667
711
  ? []
668
712
  : [
@@ -712,7 +756,7 @@ export class OpenXiangdaApplicationServices {
712
756
  if (backendDiagnostics.length) {
713
757
  return this.result("build", workspace.context.workspace, undefined, backendDiagnostics);
714
758
  }
715
- const command = this.runPackageScript(workspace.root, "build");
759
+ const command = await this.runPackageScript(workspace.root, "build");
716
760
  if (!command.ok) {
717
761
  diagnostics.push(this.diagnostic("WORKSPACE_BUILD_FAILED", "前后端生产构建失败", "package.json#scripts.build", "修复构建错误后重试", { output: command.output }));
718
762
  return this.result("build", workspace.context.workspace, undefined, diagnostics);
@@ -832,6 +876,10 @@ export class OpenXiangdaApplicationServices {
832
876
  if (diagnostics.length > 0) {
833
877
  return this.result("deployment.plan", workspace.context.workspace, undefined, diagnostics);
834
878
  }
879
+ const source = await operationStage('source', '核对远端主线与源码', () => publishedDeliverySource(workspace.root));
880
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
881
+ if (!lifecycle.readyForTest)
882
+ return this.result('deployment.plan', workspace.context.workspace, { lifecycle: summarizeLifecycle(lifecycle) }, lifecycle.diagnostics);
835
883
  const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
836
884
  return this.ok("deployment.plan", workspace.context.workspace, {
837
885
  environment: input.environment,
@@ -839,6 +887,7 @@ export class OpenXiangdaApplicationServices {
839
887
  sealed: false,
840
888
  packageDigest: null,
841
889
  buildOwner: "openxiangda deploy",
890
+ source,
842
891
  frontendRoot: workspace.config.frontend.root,
843
892
  backendRoot: workspace.config.backend.root,
844
893
  configDigest: sources.config.digest,
@@ -856,6 +905,7 @@ export class OpenXiangdaApplicationServices {
856
905
  this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy;生产环境必须显式复用成功的测试 DeploymentRun"),
857
906
  ]);
858
907
  }
908
+ const source = await operationStage('source', '核对远端主线与源码', () => publishedDeliverySource(workspace.root));
859
909
  const client = await this.client(workspace.root);
860
910
  const capabilities = await client.capabilities();
861
911
  const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
@@ -868,32 +918,59 @@ export class OpenXiangdaApplicationServices {
868
918
  });
869
919
  assertRequiredCapabilitiesAvailable(capabilities, requiredPlatformCapabilities(workspace.config));
870
920
  const hasBackend = backendRuntimeRequired(workspace.config);
871
- const target = !hasBackend || input.backendImage
872
- ? undefined
873
- : backendImageBuildTarget(capabilities, workspace.config.app.code);
921
+ const generated = await this.generate({ root: workspace.root, check: true });
922
+ if (!generated.ok)
923
+ return { ...generated, operation: 'deploy' };
924
+ const lifecycle = await operationStage('development-records', '核对需求、架构与测试验收计划', async () => {
925
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
926
+ return { ...lifecycle, ok: lifecycle.readyForTest };
927
+ });
928
+ if (!lifecycle.readyForTest)
929
+ return this.result('deploy', workspace.context.workspace, { lifecycle: summarizeLifecycle(lifecycle) }, lifecycle.diagnostics);
874
930
  const checked = await this.check(workspace.root, input.environment);
875
931
  if (!checked.ok)
876
932
  return { ...checked, operation: "deploy" };
877
- const backendImage = hasBackend
933
+ await assertDeliverySourceUnchanged(workspace.root, source);
934
+ const cache = this.deliveryCacheContext(workspace);
935
+ const preparedInputDigest = deliveryInputDigest(cache);
936
+ const reused = await operationStage('candidate', '核对可复用的不可变制品', async () => {
937
+ const candidate = await reusableSealedCandidate(cache, source, input.backendImage);
938
+ updateOperationStage('candidate', candidate ? '复用已验证的不可变制品' : '准备构建新制品', { reused: !!candidate, ...(candidate ? { packageDigest: candidate.package.digest } : {}) });
939
+ return candidate;
940
+ });
941
+ const backendImage = !reused && hasBackend
878
942
  ? input.backendImage
879
943
  ? input.backendImage
880
- : (await publishBackendImage({
944
+ : (await operationStage('backend-image', '构建并推送后端镜像', () => publishBackendImage({
881
945
  root: workspace.root,
882
946
  backendRoot: workspace.config.backend.root,
883
- target: target,
947
+ target: backendImageBuildTarget(capabilities, workspace.config.app.code),
884
948
  ...(workspace.context.workspace.revision
885
949
  ? { sourceRevision: workspace.context.workspace.revision }
886
950
  : {}),
887
- })).reference
951
+ }))).reference
888
952
  : undefined;
889
- const built = await this.buildSealed({
953
+ await assertDeliverySourceUnchanged(workspace.root, source);
954
+ const built = reused ? this.ok('build', workspace.context.workspace, {
955
+ ...reused, sealedArtifact: this.sealedArtifactStatus(workspace, reused.package.digest, true),
956
+ }) : await operationStage('seal', '校验并冻结不可变制品', () => this.buildSealed({
890
957
  ...input,
891
958
  root: workspace.root,
892
959
  ...(backendImage ? { backendImage } : {}),
893
960
  skipWorkspaceBuild: true,
894
- });
961
+ }));
895
962
  if (!built.ok || !built.data)
896
963
  return built;
964
+ if (preparedInputDigest && preparedInputDigest !== deliveryInputDigest(cache)) {
965
+ throw new DeliverySourceError('OPENXIANGDA_VALIDATION_INPUT_CHANGED', '密封期间源码、依赖或构建环境发生变化,已停止上传', '等待修改结束后重新运行 openxiangda deploy');
966
+ }
967
+ if (!reused)
968
+ recordSealedCandidate(cache, built.data.package);
969
+ const retained = await publishedDeliverySource(workspace.root, source.commit);
970
+ if (retained.repository !== source.repository) {
971
+ throw new DeliverySourceError('DELIVERY_REPOSITORY_CHANGED', '发布准备期间绑定远端发生变化', '恢复并核对原来源仓库,再发布同一候选');
972
+ }
973
+ await assertDeliverySourceUnchanged(workspace.root, source);
897
974
  const deployment = await (await import("./deployment.js")).submitAppPackage({
898
975
  client,
899
976
  capabilities,
@@ -1052,12 +1129,12 @@ export class OpenXiangdaApplicationServices {
1052
1129
  if (diagnostics.length > 0) {
1053
1130
  return this.result("deploy", workspace.context.workspace, undefined, diagnostics);
1054
1131
  }
1055
- const deployment = await client.promote({
1132
+ const deployment = await operationStage('submit', '提交生产晋级', () => client.promote({
1056
1133
  appCode: workspace.config.app.code,
1057
1134
  appVersionId,
1058
1135
  environmentKind: environment,
1059
1136
  idempotencyKey: `promote:${source.packageDigest}:${environment}`,
1060
- });
1137
+ }));
1061
1138
  return this.ok("deploy", workspace.context.workspace, deployment, [
1062
1139
  { code: "status", label: "查看生产部署状态", command: `openxiangda status ${deployment.id}` },
1063
1140
  ]);
@@ -1069,15 +1146,27 @@ export class OpenXiangdaApplicationServices {
1069
1146
  diagnostics.push(this.diagnostic("PRODUCTION_PREPRODUCTION_RUN_REQUIRED", "生产晋级来源必须是成功的预发 DeploymentRun", "deploymentId", "等待预发部署成功,并使用该 DeploymentRun ID 晋级", { status: source.status, environment: source.environment.kind }));
1070
1147
  return diagnostics;
1071
1148
  }
1072
- const versions = await client.appVersions(workspace.config.app.code, 100);
1073
- const version = versions.items.find(item => item.id === appVersionId);
1074
- if (!version) {
1075
- return [
1076
- this.diagnostic("PRODUCTION_APP_VERSION_NOT_FOUND", "预发运行引用的 AppVersion 不在当前应用版本集中", "appVersionId"),
1077
- ];
1149
+ let version;
1150
+ try {
1151
+ const capabilities = await client.capabilities();
1152
+ const { assertApplicationContractCompatible, assertProductionPromotionPreflight } = await import('./deployment.js');
1153
+ assertApplicationContractCompatible(capabilities, CURRENT_APPLICATION_CONTRACT);
1154
+ version = await operationStage('promotion-preflight', '按原测试制品预检生产环境', () => client.productionPromotionPreflight(workspace.config.app.code, source.id));
1155
+ assertProductionPromotionPreflight(version, {
1156
+ appCode: workspace.config.app.code, sourceDeploymentId: source.id, appVersionId, packageDigest: source.packageDigest,
1157
+ });
1158
+ const testedLifecycle = lifecycleAtCommit(workspace.root, version.source.commit, this.appSpecContractIndex(workspace, true));
1159
+ if (!testedLifecycle.changeId || !testedLifecycle.acceptanceIds.length) {
1160
+ diagnostics.push(this.diagnostic('APPSPEC_TESTED_PLAN_REQUIRED', '指定测试版本缺少可追溯的需求与验收计划', 'appspec', '补齐真实记录并发布新的测试候选,再执行验收和生产晋级'));
1161
+ }
1162
+ const acceptance = await operationStage('business-acceptance', '核对原测试版本的业务验收报告', async () => verifyBusinessAcceptance(workspace.root, source, testedLifecycle));
1163
+ diagnostics.push(...acceptance.diagnostics);
1164
+ }
1165
+ catch (error) {
1166
+ return [this.configurationCompatibilityDiagnostic(error)];
1078
1167
  }
1079
1168
  if (workspace.context.workspace.dirty || version.source.dirty) {
1080
- diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "提交并推送变更后重新部署预发,再晋级该 AppVersion"));
1169
+ diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "提交并推送当前改动,或在干净主线目录中晋级该测试版本"));
1081
1170
  }
1082
1171
  const repository = git(workspace.root, ["config", "--get", "remote.origin.url"]);
1083
1172
  const currentRepository = this.repositoryIdentity(repository);
@@ -1087,25 +1176,15 @@ export class OpenXiangdaApplicationServices {
1087
1176
  currentRepository !== sourceRepository) {
1088
1177
  diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_REPOSITORY_MISMATCH", "当前工作区仓库与 AppVersion 来源仓库不一致", "git.remote.origin.url", "在构建该 AppVersion 的同一仓库中执行晋级", { current: repository || null, source: version.source.repository }));
1089
1178
  }
1090
- const upstream = git(workspace.root, [
1091
- "rev-parse",
1092
- "--abbrev-ref",
1093
- "--symbolic-full-name",
1094
- "@{upstream}",
1095
- ]);
1096
- if (!upstream) {
1097
- diagnostics.push(this.diagnostic("PRODUCTION_UPSTREAM_MISSING", "当前 Git 分支没有 upstream,无法证明 AppVersion 来源已进入远端主线", "git", "为当前分支配置 upstream 并推送来源 commit"));
1179
+ if (diagnostics.length)
1180
+ return diagnostics;
1181
+ try {
1182
+ await operationStage('promotion-source', '核对测试制品的主线来源', () => publishedDeliverySource(workspace.root, version.source.commit));
1098
1183
  }
1099
- else {
1100
- const refreshed = spawnSync("git", ["-C", workspace.root, "fetch", "--quiet"], { encoding: "utf8" });
1101
- if (refreshed.status !== 0) {
1102
- diagnostics.push(this.diagnostic("PRODUCTION_UPSTREAM_REFRESH_FAILED", "无法刷新远端引用,生产晋级不能依赖可能过期的本地 upstream", "git", "恢复远端访问并重新执行晋级"));
1103
- return diagnostics;
1104
- }
1105
- const contained = spawnSync("git", ["-C", workspace.root, "merge-base", "--is-ancestor", version.source.commit, upstream], { encoding: "utf8" });
1106
- if (contained.status !== 0) {
1107
- diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_NOT_IN_UPSTREAM", "AppVersion 来源 commit 尚未包含在当前 upstream 中", "git", "推送来源 commit,确认 upstream 更新后再晋级", { commit: version.source.commit, upstream }));
1108
- }
1184
+ catch (error) {
1185
+ if (!(error instanceof DeliverySourceError))
1186
+ throw error;
1187
+ diagnostics.push(this.diagnostic(error.code, error.message, 'git', String(error.data.remediation), error.data));
1109
1188
  }
1110
1189
  return diagnostics;
1111
1190
  }
@@ -1181,7 +1260,7 @@ export class OpenXiangdaApplicationServices {
1181
1260
  try {
1182
1261
  const linkPath = join(workspace.root, ".openxiangda", "link.json");
1183
1262
  if (!existsSync(linkPath)) {
1184
- throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory>");
1263
+ throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory> --base-url <platform>");
1185
1264
  }
1186
1265
  let link;
1187
1266
  try {
@@ -1295,10 +1374,10 @@ export class OpenXiangdaApplicationServices {
1295
1374
  : {}),
1296
1375
  };
1297
1376
  }
1298
- async validateTargetConfigurationCompatibility(workspace, environmentKey) {
1377
+ async validateTargetConfigurationCompatibility(workspace, environmentKey, local) {
1299
1378
  const client = await this.client(workspace.root);
1300
1379
  const capabilities = await client.capabilities();
1301
- const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
1380
+ const { sources } = local;
1302
1381
  const required = {
1303
1382
  ...CURRENT_APPLICATION_CONTRACT,
1304
1383
  configurationBundleSchemaVersion: sources.config.value.schemaVersion,
@@ -1328,9 +1407,21 @@ export class OpenXiangdaApplicationServices {
1328
1407
  configurationDigest: sources.config.digest,
1329
1408
  contractDigest: sources.contracts.digest,
1330
1409
  }, requiredPlatformCapabilities(workspace.config));
1410
+ if (result.projectionDigest !== local.compiled.aggregateDigest) {
1411
+ throw new ControlPlaneError(409, 'OPENXIANGDA_CONFIGURATION_PROJECTION_MISMATCH', '相同校验规则与输入生成了不同投影,已停止构建,请检查平台配套版本', {
1412
+ pointer: '/projectionDigest', expectedProjectionDigest: local.compiled.aggregateDigest,
1413
+ actualProjectionDigest: result.projectionDigest, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
1414
+ });
1415
+ }
1331
1416
  return result;
1332
1417
  }
1333
1418
  configurationCompatibilityDiagnostic(error) {
1419
+ if (error instanceof NativeConfigurationCompilerError) {
1420
+ return this.diagnostic(error.code, '应用声明未通过本地完整配置校验', error.pointer, '按错误指针修正应用声明,运行 openxiangda check 复核', {
1421
+ pointer: error.pointer, identifiers: error.identifiers, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
1422
+ validationOwner: 'local-shared-compiler',
1423
+ });
1424
+ }
1334
1425
  const data = error instanceof ControlPlaneError &&
1335
1426
  error.data &&
1336
1427
  typeof error.data === "object" &&
@@ -1401,6 +1492,13 @@ export class OpenXiangdaApplicationServices {
1401
1492
  get toolchainVersion() {
1402
1493
  return this.options.toolchainVersion || OPENXIANGDA_TOOLCHAIN_VERSION;
1403
1494
  }
1495
+ deliveryCacheContext(workspace) {
1496
+ return {
1497
+ root: workspace.root, frontendRoot: workspace.config.frontend.root,
1498
+ ...(backendRuntimeRequired(workspace.config) ? { backendRoot: workspace.config.backend.root } : {}),
1499
+ toolchain: { version: this.toolchainVersion, capsule: this.toolchainCapsule, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST },
1500
+ };
1501
+ }
1404
1502
  get toolchainCapsule() {
1405
1503
  return this.options.toolchainCapsule || defaultDevkitCoreToolchainCapsule();
1406
1504
  }
@@ -1420,17 +1518,14 @@ export class OpenXiangdaApplicationServices {
1420
1518
  }];
1421
1519
  }
1422
1520
  }
1423
- runPackageScript(root, script) {
1424
- const result = spawnSync("pnpm", ["run", script], {
1425
- cwd: root,
1426
- encoding: "utf8",
1427
- env: process.env,
1521
+ packageScriptLabel(script) {
1522
+ return { check: '类型与规范检查', test: '应用测试', build: '前后端构建' }[script] || script;
1523
+ }
1524
+ async runPackageScript(root, script) {
1525
+ return operationStage(script, this.packageScriptLabel(script), async () => {
1526
+ const result = await runCommandProcess('pnpm', ['run', script], { cwd: root });
1527
+ return { ok: result.ok, exitCode: result.status ?? 1, output: result.output.trim(), outputTruncated: result.outputTruncated };
1428
1528
  });
1429
- return {
1430
- ok: result.status === 0,
1431
- exitCode: result.status ?? 1,
1432
- output: `${result.stdout || ""}${result.stderr || ""}`.trim(),
1433
- };
1434
1529
  }
1435
1530
  repositoryIdentity(value) {
1436
1531
  const normalized = String(value || "").trim();
@@ -1624,7 +1719,7 @@ export class OpenXiangdaApplicationServices {
1624
1719
  remediation: "运行 create 幂等完成工作区绑定和平台初始化",
1625
1720
  actionCode: "create",
1626
1721
  actionLabel: "初始化应用",
1627
- command: "openxiangda create <directory>",
1722
+ command: "openxiangda create <directory> --base-url <platform>",
1628
1723
  path: ".openxiangda/link.json",
1629
1724
  },
1630
1725
  OPENXIANGDA_CONNECTED_LINK_INVALID: {
@@ -1632,7 +1727,7 @@ export class OpenXiangdaApplicationServices {
1632
1727
  remediation: "运行 create 幂等校验并重新生成当前应用绑定",
1633
1728
  actionCode: "create",
1634
1729
  actionLabel: "重新初始化应用",
1635
- command: "openxiangda create <directory>",
1730
+ command: "openxiangda create <directory> --base-url <platform>",
1636
1731
  path: ".openxiangda/link.json",
1637
1732
  },
1638
1733
  OPENXIANGDA_CONNECTED_LOGIN_REQUIRED: {
@@ -1706,7 +1801,7 @@ export class OpenXiangdaApplicationServices {
1706
1801
  operation,
1707
1802
  workspace,
1708
1803
  ...(data === undefined ? {} : { data }),
1709
- diagnostics,
1804
+ diagnostics: [...diagnostics].sort((left, right) => ({ error: 0, warning: 1, info: 2 })[left.severity] - ({ error: 0, warning: 1, info: 2 })[right.severity]),
1710
1805
  nextActions,
1711
1806
  };
1712
1807
  }