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.
- package/dist/app-spec.d.ts +23 -11
- package/dist/app-spec.d.ts.map +1 -1
- package/dist/app-spec.js +171 -52
- package/dist/app-spec.js.map +1 -1
- package/dist/application-services.d.ts +290 -23
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +174 -79
- package/dist/application-services.js.map +1 -1
- package/dist/backend-image-build.js +6 -11
- package/dist/backend-image-build.js.map +1 -1
- package/dist/command-process.d.ts +14 -0
- package/dist/command-process.d.ts.map +1 -0
- package/dist/command-process.js +51 -0
- package/dist/command-process.js.map +1 -0
- package/dist/command-registry.d.ts +2 -2
- package/dist/command-registry.js +1 -1
- package/dist/command-registry.js.map +1 -1
- package/dist/compiler/ai-catalog.d.ts.map +1 -1
- package/dist/compiler/ai-catalog.js +2 -1
- package/dist/compiler/ai-catalog.js.map +1 -1
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +2 -1
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +14 -7
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/permission-review.d.ts.map +1 -1
- package/dist/configuration-preflight.d.ts +7 -0
- package/dist/configuration-preflight.d.ts.map +1 -0
- package/dist/configuration-preflight.js +13 -0
- package/dist/configuration-preflight.js.map +1 -0
- package/dist/control-plane-client.d.ts +8 -1
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +13 -0
- package/dist/control-plane-client.js.map +1 -1
- package/dist/delivery-cache.d.ts +34 -0
- package/dist/delivery-cache.d.ts.map +1 -0
- package/dist/delivery-cache.js +186 -0
- package/dist/delivery-cache.js.map +1 -0
- package/dist/delivery-source.d.ts +17 -0
- package/dist/delivery-source.d.ts.map +1 -0
- package/dist/delivery-source.js +85 -0
- package/dist/delivery-source.js.map +1 -0
- package/dist/deployment-observer.d.ts +14 -0
- package/dist/deployment-observer.d.ts.map +1 -0
- package/dist/deployment-observer.js +68 -0
- package/dist/deployment-observer.js.map +1 -0
- package/dist/deployment.d.ts +13 -0
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +34 -7
- package/dist/deployment.js.map +1 -1
- package/dist/developer-operations.d.ts +147 -43
- package/dist/developer-operations.d.ts.map +1 -1
- package/dist/developer-operations.js +22 -3
- package/dist/developer-operations.js.map +1 -1
- package/dist/development-lifecycle.d.ts +48 -0
- package/dist/development-lifecycle.d.ts.map +1 -0
- package/dist/development-lifecycle.js +180 -0
- package/dist/development-lifecycle.js.map +1 -0
- package/dist/documentation.d.ts +4 -4
- package/dist/documentation.d.ts.map +1 -1
- package/dist/documentation.js +4 -5
- package/dist/documentation.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/operation-progress.d.ts +26 -0
- package/dist/operation-progress.d.ts.map +1 -0
- package/dist/operation-progress.js +92 -0
- package/dist/operation-progress.js.map +1 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
-
|
|
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
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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", "
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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 =
|
|
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
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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
|
}
|