openxiangda-devkit-core 2.0.0-alpha.98 → 2.1.0
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/LICENSE +21 -0
- package/README.md +3 -9
- package/dist/app-spec.d.ts +30 -12
- package/dist/app-spec.d.ts.map +1 -1
- package/dist/app-spec.js +238 -57
- package/dist/app-spec.js.map +1 -1
- package/dist/application-services.d.ts +625 -42
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +322 -85
- package/dist/application-services.js.map +1 -1
- package/dist/application-ui-contract.d.ts +5 -0
- package/dist/application-ui-contract.d.ts.map +1 -0
- package/dist/application-ui-contract.js +149 -0
- package/dist/application-ui-contract.js.map +1 -0
- package/dist/backend-image-build.d.ts +6 -0
- package/dist/backend-image-build.d.ts.map +1 -1
- package/dist/backend-image-build.js +51 -11
- package/dist/backend-image-build.js.map +1 -1
- package/dist/backend-image-upload.d.ts +34 -0
- package/dist/backend-image-upload.d.ts.map +1 -0
- package/dist/backend-image-upload.js +118 -0
- package/dist/backend-image-upload.js.map +1 -0
- 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 +44 -4
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +10 -6
- 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/application-model.d.ts +60 -0
- package/dist/compiler/application-model.d.ts.map +1 -0
- package/dist/compiler/application-model.js +141 -0
- package/dist/compiler/application-model.js.map +1 -0
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +196 -98
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +36 -10
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +536 -171
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/field-surface.js +1 -1
- package/dist/compiler/field-surface.js.map +1 -1
- package/dist/compiler/package-compiler.d.ts.map +1 -1
- package/dist/compiler/package-compiler.js +12 -0
- package/dist/compiler/package-compiler.js.map +1 -1
- package/dist/compiler/permission-review.d.ts +139 -0
- package/dist/compiler/permission-review.d.ts.map +1 -0
- package/dist/compiler/permission-review.js +131 -0
- package/dist/compiler/permission-review.js.map +1 -0
- 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 +66 -61
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +99 -8
- 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 +17 -1
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +55 -15
- package/dist/deployment.js.map +1 -1
- package/dist/design-readiness.d.ts +24 -0
- package/dist/design-readiness.d.ts.map +1 -0
- package/dist/design-readiness.js +102 -0
- package/dist/design-readiness.js.map +1 -0
- package/dist/developer-errors.d.ts +11 -0
- package/dist/developer-errors.d.ts.map +1 -0
- package/dist/developer-errors.js +16 -0
- package/dist/developer-errors.js.map +1 -0
- package/dist/developer-operations.d.ts +190 -0
- package/dist/developer-operations.d.ts.map +1 -0
- package/dist/developer-operations.js +65 -0
- package/dist/developer-operations.js.map +1 -0
- package/dist/development-lifecycle.d.ts +75 -0
- package/dist/development-lifecycle.d.ts.map +1 -0
- package/dist/development-lifecycle.js +189 -0
- package/dist/development-lifecycle.js.map +1 -0
- package/dist/documentation.d.ts +111 -0
- package/dist/documentation.d.ts.map +1 -0
- package/dist/documentation.js +112 -0
- package/dist/documentation.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -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/dist/optional-backend.d.ts +14 -0
- package/dist/optional-backend.d.ts.map +1 -0
- package/dist/optional-backend.js +145 -0
- package/dist/optional-backend.js.map +1 -0
- package/dist/session.d.ts +22 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +36 -0
- package/dist/session.js.map +1 -1
- package/dist/workspace-loader.d.ts.map +1 -1
- package/dist/workspace-loader.js +17 -2
- package/dist/workspace-loader.js.map +1 -1
- package/dist/workspace-operation.d.ts +3 -0
- package/dist/workspace-operation.d.ts.map +1 -0
- package/dist/workspace-operation.js +45 -0
- package/dist/workspace-operation.js.map +1 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +1 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +18 -4
- package/templates/backend/Dockerfile +20 -0
- package/templates/backend/package.json +27 -0
- package/templates/backend/src/app.module.ts +23 -0
- package/templates/backend/src/main.ts +5 -0
- package/templates/backend/tsconfig.json +9 -0
|
@@ -1,8 +1,14 @@
|
|
|
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
|
-
import { CONFIGURATION_COMPATIBILITY_CAPABILITY, CURRENT_APPLICATION_CONTRACT, OPENXIANGDA_CONTRACT_VERSION, SCHEMA_VERSIONS, canonicalJson, sha256Digest, } from "openxiangda-contracts";
|
|
11
|
+
import { CONFIGURATION_COMPATIBILITY_CAPABILITY, CURRENT_APPLICATION_CONTRACT, OPENXIANGDA_CONTRACT_VERSION, SCHEMA_VERSIONS, RUNTIME_CAPACITY_PREFLIGHT_SCHEMA, canonicalJson, sha256Digest, } from "openxiangda-contracts";
|
|
6
12
|
import { compileApplicationSources, compileAppPackage, requiredPlatformCapabilities, backendRuntimeRequired, renderAdminNavigationSuggestion, sha256Bytes, validateAppConfig, } from "./compiler/index.js";
|
|
7
13
|
import { OpenXiangdaControlPlaneClient, ControlPlaneError, } from "./control-plane-client.js";
|
|
8
14
|
import { normalizePlatformBaseUrl, OpenXiangdaDeveloperSession, } from "./session.js";
|
|
@@ -10,6 +16,9 @@ import { runConnectedDevelopment, selectConnectedDevelopmentEnvironment, } from
|
|
|
10
16
|
import { backendImageBuildTarget, publishBackendImage, } from "./backend-image-build.js";
|
|
11
17
|
import { OPENXIANGDA_TOOLCHAIN_VERSION } from "./version.js";
|
|
12
18
|
import { git, loadWorkspace, } from "./workspace-loader.js";
|
|
19
|
+
import { validateApplicationUiContract } from './application-ui-contract.js';
|
|
20
|
+
import { initializeOptionalBackend } from './optional-backend.js';
|
|
21
|
+
import { buildPermissionReview } from './compiler/permission-review.js';
|
|
13
22
|
import { validateNestInjectionContract } from "./nest-injection-contract.js";
|
|
14
23
|
import { collectWorkspaceToolchainDependencies, defaultDevkitCoreToolchainCapsule, ToolchainCapsuleScanLimitError, toolchainCapsuleDiagnostic, } from "./toolchain-capsule.js";
|
|
15
24
|
import { advisoryAppSpecDiagnostics, closeAppSpecChange, createAppSpecCapability, createAppSpecChange, initializeAppSpec, inspectAppSpec, summarizeAppSpecContext, } from "./app-spec.js";
|
|
@@ -21,7 +30,15 @@ export class OpenXiangdaApplicationServices {
|
|
|
21
30
|
}
|
|
22
31
|
async workspaceContext(root) {
|
|
23
32
|
const workspace = await this.workspace(root);
|
|
24
|
-
|
|
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.designs, ...lifecycle.context.index.history,
|
|
40
|
+
].map(({ id, title, status, path, kind }) => ({ id, title, status, path, kind })),
|
|
41
|
+
} });
|
|
25
42
|
}
|
|
26
43
|
async appSpecInit(root) {
|
|
27
44
|
const workspace = await this.workspace(root);
|
|
@@ -74,15 +91,16 @@ export class OpenXiangdaApplicationServices {
|
|
|
74
91
|
},
|
|
75
92
|
]);
|
|
76
93
|
}
|
|
77
|
-
async appSpecContext(root, selector) {
|
|
94
|
+
async appSpecContext(root, selector, historyOffset = 0) {
|
|
78
95
|
const workspace = await this.workspace(root);
|
|
79
|
-
const context = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, true), selector);
|
|
80
|
-
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), undefined, selector)) }, advisoryAppSpecDiagnostics(context.diagnostics));
|
|
81
98
|
}
|
|
82
99
|
async appSpecCheck(root) {
|
|
83
100
|
const workspace = await this.workspace(root);
|
|
84
101
|
const context = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, true));
|
|
85
|
-
|
|
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, [
|
|
86
104
|
{
|
|
87
105
|
code: "check",
|
|
88
106
|
label: "继续运行应用检查",
|
|
@@ -90,6 +108,13 @@ export class OpenXiangdaApplicationServices {
|
|
|
90
108
|
},
|
|
91
109
|
]);
|
|
92
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
|
+
}
|
|
93
118
|
async appSpecClose(input) {
|
|
94
119
|
const workspace = await this.workspace(input.root);
|
|
95
120
|
const outcome = closeAppSpecChange({
|
|
@@ -152,6 +177,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
152
177
|
aiCatalogDigest: sources.aiCatalog.digest,
|
|
153
178
|
contract: sources.contracts.value,
|
|
154
179
|
aiCatalog: sources.aiCatalog.value,
|
|
180
|
+
permissionReview: buildPermissionReview(sources.config.value, sources.contracts.value),
|
|
155
181
|
adminNavigationAuthoring: {
|
|
156
182
|
schemaVersion: "openxiangda.admin-navigation-authoring/v1",
|
|
157
183
|
owner: "application",
|
|
@@ -176,11 +202,70 @@ export class OpenXiangdaApplicationServices {
|
|
|
176
202
|
},
|
|
177
203
|
});
|
|
178
204
|
}
|
|
205
|
+
async contractContext(root, input = {}) {
|
|
206
|
+
const result = await this.contractDescribe(root);
|
|
207
|
+
const data = result.data;
|
|
208
|
+
const groups = {
|
|
209
|
+
resource: data.contract.resources,
|
|
210
|
+
operation: data.contract.operations,
|
|
211
|
+
workflow: data.contract.workflows,
|
|
212
|
+
route: data.contract.routes,
|
|
213
|
+
capability: data.aiCatalog.capabilities,
|
|
214
|
+
};
|
|
215
|
+
const entries = Object.entries(groups).flatMap(([kind, values]) => values.map(value => ({
|
|
216
|
+
selector: `${kind}:${String(value.code || value.resourceCode || value.id)}`,
|
|
217
|
+
kind, code: String(value.code || value.resourceCode || value.id),
|
|
218
|
+
title: String(value.name || value.label || value.title || value.code || value.resourceCode || value.id),
|
|
219
|
+
value,
|
|
220
|
+
})));
|
|
221
|
+
const selector = input.selector || 'index';
|
|
222
|
+
const offset = input.offset ?? 0;
|
|
223
|
+
const limit = input.limit ?? 30;
|
|
224
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
|
|
225
|
+
throw new Error('CONTRACT_SELECTION_INVALID: offset 必须为非负整数,limit 必须在 1 到 100 之间');
|
|
226
|
+
}
|
|
227
|
+
let selection;
|
|
228
|
+
if (selector === 'index')
|
|
229
|
+
selection = {
|
|
230
|
+
total: entries.length, offset, limit, nextOffset: offset + limit < entries.length ? offset + limit : null,
|
|
231
|
+
sections: ['navigation', 'permissions', 'all'],
|
|
232
|
+
entries: entries.slice(offset, offset + limit).map(({ value: _, ...entry }) => entry),
|
|
233
|
+
};
|
|
234
|
+
else if (selector === 'navigation')
|
|
235
|
+
selection = { adminNavigationAuthoring: data.adminNavigationAuthoring };
|
|
236
|
+
else if (selector === 'permissions')
|
|
237
|
+
selection = { permissionReview: data.permissionReview };
|
|
238
|
+
else if (selector === 'all')
|
|
239
|
+
selection = data;
|
|
240
|
+
else {
|
|
241
|
+
const entry = entries.find(item => item.selector === selector);
|
|
242
|
+
if (!entry)
|
|
243
|
+
throw new Error(`CONTRACT_SELECTION_NOT_FOUND: 未知契约选择器 ${selector},先读取 index`);
|
|
244
|
+
selection = entry.value;
|
|
245
|
+
}
|
|
246
|
+
if (Buffer.byteLength(JSON.stringify(selection)) > 256 * 1024)
|
|
247
|
+
throw new Error('CONTRACT_SELECTION_TOO_LARGE: 选择结果超过 256 KiB,请按模型或能力读取');
|
|
248
|
+
return { ...result, data: { configDigest: data.configDigest, contractDigest: data.contractDigest, aiCatalogDigest: data.aiCatalogDigest, counts: data.counts, selector, selection } };
|
|
249
|
+
}
|
|
179
250
|
async platformCapabilities(root) {
|
|
180
251
|
const workspace = await this.workspace(root);
|
|
181
252
|
const capabilities = await (await this.client(workspace.root)).capabilities();
|
|
182
253
|
return this.ok("platform.capabilities", workspace.context.workspace, capabilities);
|
|
183
254
|
}
|
|
255
|
+
async administrationContext(root, environmentKey = "preproduction") {
|
|
256
|
+
const workspace = await this.workspace(root);
|
|
257
|
+
const environment = this.deploymentEnvironment(environmentKey);
|
|
258
|
+
const client = await this.client(workspace.root);
|
|
259
|
+
return this.ok("admin.context", workspace.context.workspace, await client.applicationAdministrationContext(workspace.config.app.code, environment));
|
|
260
|
+
}
|
|
261
|
+
async workflowNodeConfigurations(root, workflowCode, environmentKey = "preproduction") {
|
|
262
|
+
const workspace = await this.workspace(root);
|
|
263
|
+
const environment = this.deploymentEnvironment(environmentKey);
|
|
264
|
+
if (!workflowCode || workflowCode.length > 128)
|
|
265
|
+
throw new Error("WORKFLOW_CODE_REQUIRED");
|
|
266
|
+
const client = await this.client(workspace.root);
|
|
267
|
+
return this.ok("admin.workflow", workspace.context.workspace, await client.workflowNodeConfigurations(workspace.config.app.code, workflowCode, environment));
|
|
268
|
+
}
|
|
184
269
|
async provisionApplication(root) {
|
|
185
270
|
const workspace = await this.workspace(root);
|
|
186
271
|
const capsuleDiagnostics = this.toolchainDiagnostics(workspace);
|
|
@@ -512,11 +597,14 @@ export class OpenXiangdaApplicationServices {
|
|
|
512
597
|
async check(root, environmentKey) {
|
|
513
598
|
const workspace = await this.workspace(root);
|
|
514
599
|
const appSpec = inspectAppSpec(workspace.root, this.appSpecContractIndex(workspace, false));
|
|
600
|
+
const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, false));
|
|
515
601
|
const diagnostics = [
|
|
516
602
|
...validateAppConfig(workspace.config),
|
|
517
603
|
...this.versionTrainDiagnostics(workspace),
|
|
518
604
|
...validateNestInjectionContract(workspace.root),
|
|
605
|
+
...validateApplicationUiContract(workspace.root, workspace.config.frontend.root),
|
|
519
606
|
...advisoryAppSpecDiagnostics(appSpec.diagnostics),
|
|
607
|
+
...advisoryAppSpecDiagnostics(lifecycle.diagnostics),
|
|
520
608
|
];
|
|
521
609
|
if (diagnostics.some(item => item.severity === "error")) {
|
|
522
610
|
const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
|
|
@@ -525,10 +613,28 @@ export class OpenXiangdaApplicationServices {
|
|
|
525
613
|
compatibility: null,
|
|
526
614
|
stages: [],
|
|
527
615
|
sealedArtifact,
|
|
528
|
-
appSpec: summarizeAppSpecContext(appSpec),
|
|
616
|
+
appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
|
|
529
617
|
}, diagnostics);
|
|
530
618
|
}
|
|
531
|
-
|
|
619
|
+
let compatibility = null;
|
|
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));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
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 }));
|
|
532
638
|
diagnostics.push(...generated.diagnostics);
|
|
533
639
|
if (!generated.ok) {
|
|
534
640
|
const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
|
|
@@ -537,34 +643,45 @@ export class OpenXiangdaApplicationServices {
|
|
|
537
643
|
compatibility: null,
|
|
538
644
|
stages: [],
|
|
539
645
|
sealedArtifact,
|
|
540
|
-
appSpec: summarizeAppSpecContext(appSpec),
|
|
646
|
+
appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
|
|
541
647
|
}, diagnostics, generated.nextActions);
|
|
542
648
|
}
|
|
543
|
-
|
|
544
|
-
if (
|
|
545
|
-
|
|
546
|
-
|
|
649
|
+
const backendDiagnostics = this.initializeBackend(workspace);
|
|
650
|
+
if (backendDiagnostics.length) {
|
|
651
|
+
return this.result("check", workspace.context.workspace, undefined, backendDiagnostics);
|
|
652
|
+
}
|
|
653
|
+
const stages = [];
|
|
654
|
+
const cache = this.deliveryCacheContext(workspace);
|
|
655
|
+
const inputDigest = deliveryInputDigest(cache);
|
|
656
|
+
const reusable = reusableValidation(cache, inputDigest);
|
|
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;
|
|
547
664
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
generated: generated.data,
|
|
553
|
-
compatibility: null,
|
|
554
|
-
stages: [],
|
|
555
|
-
sealedArtifact,
|
|
556
|
-
appSpec: summarizeAppSpecContext(appSpec),
|
|
557
|
-
}, diagnostics);
|
|
665
|
+
if (stages.some(stage => stage.state === 'failed')) {
|
|
666
|
+
stages.push({ name, state: 'skipped', ok: false, exitCode: null, output: '前置阶段失败,未执行' });
|
|
667
|
+
skippedOperationStage(name, this.packageScriptLabel(name));
|
|
668
|
+
continue;
|
|
558
669
|
}
|
|
670
|
+
const result = await this.runPackageScript(workspace.root, name);
|
|
671
|
+
stages.push({ name, ...result, state: result.ok ? 'passed' : 'failed' });
|
|
559
672
|
}
|
|
560
|
-
const
|
|
561
|
-
name,
|
|
562
|
-
...this.runPackageScript(workspace.root, name),
|
|
563
|
-
}));
|
|
564
|
-
for (const stage of stages.filter(item => !item.ok)) {
|
|
673
|
+
for (const stage of stages.filter(item => item.state === 'failed')) {
|
|
565
674
|
const code = `WORKSPACE_${stage.name.toUpperCase()}_FAILED`;
|
|
566
675
|
diagnostics.push(this.diagnostic(code, `工作区 ${stage.name} 脚本失败`, `package.json#scripts.${stage.name}`, "修复失败后重新运行 openxiangda check", { output: stage.output }));
|
|
567
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
|
+
}
|
|
568
685
|
const passed = !diagnostics.some(item => item.severity === "error");
|
|
569
686
|
const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, passed);
|
|
570
687
|
return this.result("check", workspace.context.workspace, {
|
|
@@ -572,7 +689,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
572
689
|
compatibility,
|
|
573
690
|
stages,
|
|
574
691
|
sealedArtifact,
|
|
575
|
-
appSpec: summarizeAppSpecContext(appSpec),
|
|
692
|
+
appSpec: { ...summarizeAppSpecContext(appSpec), lifecycle: summarizeLifecycle(lifecycle) },
|
|
576
693
|
}, diagnostics, passed
|
|
577
694
|
? [
|
|
578
695
|
{
|
|
@@ -589,7 +706,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
589
706
|
if (diagnostics.length > 0) {
|
|
590
707
|
return this.result("test", workspace.context.workspace, undefined, diagnostics);
|
|
591
708
|
}
|
|
592
|
-
const command = this.runPackageScript(workspace.root, "test");
|
|
709
|
+
const command = await this.runPackageScript(workspace.root, "test");
|
|
593
710
|
const commandDiagnostics = command.ok
|
|
594
711
|
? []
|
|
595
712
|
: [
|
|
@@ -635,7 +752,11 @@ export class OpenXiangdaApplicationServices {
|
|
|
635
752
|
return this.result("build", workspace.context.workspace, undefined, generated.diagnostics, generated.nextActions);
|
|
636
753
|
}
|
|
637
754
|
if (!input.skipWorkspaceBuild) {
|
|
638
|
-
const
|
|
755
|
+
const backendDiagnostics = this.initializeBackend(workspace);
|
|
756
|
+
if (backendDiagnostics.length) {
|
|
757
|
+
return this.result("build", workspace.context.workspace, undefined, backendDiagnostics);
|
|
758
|
+
}
|
|
759
|
+
const command = await this.runPackageScript(workspace.root, "build");
|
|
639
760
|
if (!command.ok) {
|
|
640
761
|
diagnostics.push(this.diagnostic("WORKSPACE_BUILD_FAILED", "前后端生产构建失败", "package.json#scripts.build", "修复构建错误后重试", { output: command.output }));
|
|
641
762
|
return this.result("build", workspace.context.workspace, undefined, diagnostics);
|
|
@@ -755,13 +876,22 @@ export class OpenXiangdaApplicationServices {
|
|
|
755
876
|
if (diagnostics.length > 0) {
|
|
756
877
|
return this.result("deployment.plan", workspace.context.workspace, undefined, diagnostics);
|
|
757
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);
|
|
758
883
|
const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
|
|
884
|
+
const client = await this.client(workspace.root);
|
|
885
|
+
const candidate = await reusableSealedCandidate(this.deliveryCacheContext(workspace), source);
|
|
886
|
+
const runtimeCapacity = await this.runtimeCapacityPlan(workspace, client, await client.capabilities(), input, candidate?.package.digest);
|
|
759
887
|
return this.ok("deployment.plan", workspace.context.workspace, {
|
|
888
|
+
runtimeCapacity,
|
|
760
889
|
environment: input.environment,
|
|
761
890
|
appCode: workspace.config.app.code,
|
|
762
891
|
sealed: false,
|
|
763
892
|
packageDigest: null,
|
|
764
893
|
buildOwner: "openxiangda deploy",
|
|
894
|
+
source,
|
|
765
895
|
frontendRoot: workspace.config.frontend.root,
|
|
766
896
|
backendRoot: workspace.config.backend.root,
|
|
767
897
|
configDigest: sources.config.digest,
|
|
@@ -779,6 +909,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
779
909
|
this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy;生产环境必须显式复用成功的测试 DeploymentRun"),
|
|
780
910
|
]);
|
|
781
911
|
}
|
|
912
|
+
const source = await operationStage('source', '核对远端主线与源码', () => publishedDeliverySource(workspace.root));
|
|
782
913
|
const client = await this.client(workspace.root);
|
|
783
914
|
const capabilities = await client.capabilities();
|
|
784
915
|
const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
|
|
@@ -791,41 +922,78 @@ export class OpenXiangdaApplicationServices {
|
|
|
791
922
|
});
|
|
792
923
|
assertRequiredCapabilitiesAvailable(capabilities, requiredPlatformCapabilities(workspace.config));
|
|
793
924
|
const hasBackend = backendRuntimeRequired(workspace.config);
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
925
|
+
const cache = this.deliveryCacheContext(workspace);
|
|
926
|
+
const capacityCandidate = await reusableSealedCandidate(cache, source, input.backendImage);
|
|
927
|
+
const runtimeCapacity = await this.runtimeCapacityPlan(workspace, client, capabilities, input, capacityCandidate?.package.digest);
|
|
928
|
+
if (runtimeCapacity.sufficient === false) {
|
|
929
|
+
throw new ControlPlaneError(409, 'APPLICATION_V2_RUNTIME_QUOTA_INSUFFICIENT', '应用运行资源不足,已在检查脚本与镜像构建前停止;请释放闲置后端或调整平台配额后重试', runtimeCapacity.capacity);
|
|
930
|
+
}
|
|
931
|
+
const generated = await this.generate({ root: workspace.root, check: true });
|
|
932
|
+
if (!generated.ok)
|
|
933
|
+
return { ...generated, operation: 'deploy' };
|
|
934
|
+
const lifecycle = await operationStage('development-records', '核对需求、架构与测试验收计划', async () => {
|
|
935
|
+
const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
|
|
936
|
+
return { ...lifecycle, ok: lifecycle.readyForTest };
|
|
937
|
+
});
|
|
938
|
+
if (!lifecycle.readyForTest)
|
|
939
|
+
return this.result('deploy', workspace.context.workspace, { lifecycle: summarizeLifecycle(lifecycle) }, lifecycle.diagnostics);
|
|
797
940
|
const checked = await this.check(workspace.root, input.environment);
|
|
798
941
|
if (!checked.ok)
|
|
799
942
|
return { ...checked, operation: "deploy" };
|
|
800
|
-
|
|
943
|
+
await assertDeliverySourceUnchanged(workspace.root, source);
|
|
944
|
+
const preparedInputDigest = deliveryInputDigest(cache);
|
|
945
|
+
const reused = await operationStage('candidate', '核对可复用的不可变制品', async () => {
|
|
946
|
+
const candidate = await reusableSealedCandidate(cache, source, input.backendImage);
|
|
947
|
+
updateOperationStage('candidate', candidate ? '复用已验证的不可变制品' : '准备构建新制品', { reused: !!candidate, ...(candidate ? { packageDigest: candidate.package.digest } : {}) });
|
|
948
|
+
return candidate;
|
|
949
|
+
});
|
|
950
|
+
if (capacityCandidate && capacityCandidate.package.digest !== reused?.package.digest) {
|
|
951
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_RUNTIME_CAPACITY_CANDIDATE_CHANGED', '检查过程中可复用制品发生变化,请重新预检后部署');
|
|
952
|
+
}
|
|
953
|
+
const backendImage = !reused && hasBackend
|
|
801
954
|
? input.backendImage
|
|
802
955
|
? input.backendImage
|
|
803
|
-
: (await publishBackendImage({
|
|
956
|
+
: (await operationStage('backend-image', '构建并上传后端镜像', () => publishBackendImage({
|
|
804
957
|
root: workspace.root,
|
|
805
958
|
backendRoot: workspace.config.backend.root,
|
|
806
|
-
target:
|
|
959
|
+
target: backendImageBuildTarget(capabilities, workspace.config.app.code),
|
|
960
|
+
uploader: client,
|
|
807
961
|
...(workspace.context.workspace.revision
|
|
808
962
|
? { sourceRevision: workspace.context.workspace.revision }
|
|
809
963
|
: {}),
|
|
810
|
-
})).reference
|
|
964
|
+
}))).reference
|
|
811
965
|
: undefined;
|
|
812
|
-
|
|
966
|
+
await assertDeliverySourceUnchanged(workspace.root, source);
|
|
967
|
+
const built = reused ? this.ok('build', workspace.context.workspace, {
|
|
968
|
+
...reused, sealedArtifact: this.sealedArtifactStatus(workspace, reused.package.digest, true),
|
|
969
|
+
}) : await operationStage('seal', '校验并冻结不可变制品', () => this.buildSealed({
|
|
813
970
|
...input,
|
|
814
971
|
root: workspace.root,
|
|
815
972
|
...(backendImage ? { backendImage } : {}),
|
|
816
973
|
skipWorkspaceBuild: true,
|
|
817
|
-
});
|
|
974
|
+
}));
|
|
818
975
|
if (!built.ok || !built.data)
|
|
819
976
|
return built;
|
|
977
|
+
if (preparedInputDigest && preparedInputDigest !== deliveryInputDigest(cache)) {
|
|
978
|
+
throw new DeliverySourceError('OPENXIANGDA_VALIDATION_INPUT_CHANGED', '密封期间源码、依赖或构建环境发生变化,已停止上传', '等待修改结束后重新运行 openxiangda deploy');
|
|
979
|
+
}
|
|
980
|
+
if (!reused)
|
|
981
|
+
recordSealedCandidate(cache, built.data.package);
|
|
982
|
+
const retained = await publishedDeliverySource(workspace.root, source.commit);
|
|
983
|
+
if (retained.repository !== source.repository) {
|
|
984
|
+
throw new DeliverySourceError('DELIVERY_REPOSITORY_CHANGED', '发布准备期间绑定远端发生变化', '恢复并核对原来源仓库,再发布同一候选');
|
|
985
|
+
}
|
|
986
|
+
await assertDeliverySourceUnchanged(workspace.root, source);
|
|
820
987
|
const deployment = await (await import("./deployment.js")).submitAppPackage({
|
|
821
988
|
client,
|
|
822
989
|
capabilities,
|
|
823
990
|
compiledPackage: built.data.package,
|
|
824
991
|
artifactContent: built.data.artifactContent,
|
|
825
992
|
environmentKind: input.environment,
|
|
993
|
+
...(input.deploymentStrategy ? { deploymentStrategy: input.deploymentStrategy } : {}),
|
|
826
994
|
...(input.environmentId ? { environmentId: input.environmentId } : {}),
|
|
827
995
|
idempotencyKey: input.idempotencyKey ||
|
|
828
|
-
`deploy:${built.data.package.digest}:${input.environment}`,
|
|
996
|
+
`deploy:${built.data.package.digest}:${input.environment}${input.deploymentStrategy === 'maintenance-replace' ? ':maintenance-replace' : ''}`,
|
|
829
997
|
...(input.requestId ? { requestId: input.requestId } : {}),
|
|
830
998
|
});
|
|
831
999
|
return this.ok("deploy", workspace.context.workspace, deployment, [
|
|
@@ -836,6 +1004,21 @@ export class OpenXiangdaApplicationServices {
|
|
|
836
1004
|
},
|
|
837
1005
|
]);
|
|
838
1006
|
}
|
|
1007
|
+
async runtimeCapacityPlan(workspace, client, capabilities, input, packageDigest) {
|
|
1008
|
+
const result = await operationStage('runtime-capacity', '只读预检运行资源配额', () => client.runtimeCapacityPreflight(workspace.config.app.code, capabilities, {
|
|
1009
|
+
schemaVersion: RUNTIME_CAPACITY_PREFLIGHT_SCHEMA,
|
|
1010
|
+
...(input.deploymentStrategy ? { deploymentStrategy: input.deploymentStrategy } : {}),
|
|
1011
|
+
environmentKey: input.environment,
|
|
1012
|
+
...(input.environmentId ? { environmentId: input.environmentId } : {}),
|
|
1013
|
+
backend: backendRuntimeRequired(workspace.config) ? { isolation: workspace.config.backend.isolation || 'shared', resourceProfile: workspace.config.backend.resourceProfile || 'light' } : null,
|
|
1014
|
+
...(packageDigest ? { packageDigest, idempotencyKey: input.idempotencyKey || `deploy:${packageDigest}:${input.environment}${input.deploymentStrategy === 'maintenance-replace' ? ':maintenance-replace' : ''}` } : {}),
|
|
1015
|
+
}));
|
|
1016
|
+
const { assertRuntimeCapacityPreflight } = await import('./deployment.js');
|
|
1017
|
+
assertRuntimeCapacityPreflight(result, input.environmentId, input.deploymentStrategy);
|
|
1018
|
+
if (result.basis === 'existing-run' && !packageDigest)
|
|
1019
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_RUNTIME_CAPACITY_PREFLIGHT_RESULT_INVALID', '平台返回了未绑定本地候选的原运行');
|
|
1020
|
+
return result;
|
|
1021
|
+
}
|
|
839
1022
|
async deploymentStatus(root, deploymentId) {
|
|
840
1023
|
const workspace = await this.workspace(root);
|
|
841
1024
|
const client = await this.client(workspace.root);
|
|
@@ -945,6 +1128,20 @@ export class OpenXiangdaApplicationServices {
|
|
|
945
1128
|
: []),
|
|
946
1129
|
];
|
|
947
1130
|
}
|
|
1131
|
+
async productionDeploymentPlan(root, deploymentId) {
|
|
1132
|
+
const workspace = await this.workspace(root);
|
|
1133
|
+
const client = await this.client(workspace.root);
|
|
1134
|
+
const source = await client.deployment(workspace.config.app.code, deploymentId);
|
|
1135
|
+
const appVersionId = String(source.result?.applicationVersionId || '');
|
|
1136
|
+
if (!appVersionId)
|
|
1137
|
+
throw new Error('DEPLOYMENT_APP_VERSION_NOT_AVAILABLE: 测试运行尚无可晋级版本');
|
|
1138
|
+
const diagnostics = await this.productionPromotionDiagnostics(workspace, client, source, appVersionId);
|
|
1139
|
+
return this.result('deployment.plan', workspace.context.workspace, {
|
|
1140
|
+
environment: 'production', sourceDeploymentId: deploymentId, appVersionId,
|
|
1141
|
+
packageDigest: source.packageDigest, sealed: true, submitted: false,
|
|
1142
|
+
rebuild: false, businessAcceptance: '需要核对该测试版本的业务验收记录',
|
|
1143
|
+
}, diagnostics);
|
|
1144
|
+
}
|
|
948
1145
|
async deployProduction(root, deploymentId) {
|
|
949
1146
|
const workspace = await this.workspace(root);
|
|
950
1147
|
const capsuleDiagnostics = this.toolchainDiagnostics(workspace);
|
|
@@ -961,12 +1158,12 @@ export class OpenXiangdaApplicationServices {
|
|
|
961
1158
|
if (diagnostics.length > 0) {
|
|
962
1159
|
return this.result("deploy", workspace.context.workspace, undefined, diagnostics);
|
|
963
1160
|
}
|
|
964
|
-
const deployment = await client.promote({
|
|
1161
|
+
const deployment = await operationStage('submit', '提交生产晋级', () => client.promote({
|
|
965
1162
|
appCode: workspace.config.app.code,
|
|
966
1163
|
appVersionId,
|
|
967
1164
|
environmentKind: environment,
|
|
968
1165
|
idempotencyKey: `promote:${source.packageDigest}:${environment}`,
|
|
969
|
-
});
|
|
1166
|
+
}));
|
|
970
1167
|
return this.ok("deploy", workspace.context.workspace, deployment, [
|
|
971
1168
|
{ code: "status", label: "查看生产部署状态", command: `openxiangda status ${deployment.id}` },
|
|
972
1169
|
]);
|
|
@@ -978,15 +1175,29 @@ export class OpenXiangdaApplicationServices {
|
|
|
978
1175
|
diagnostics.push(this.diagnostic("PRODUCTION_PREPRODUCTION_RUN_REQUIRED", "生产晋级来源必须是成功的预发 DeploymentRun", "deploymentId", "等待预发部署成功,并使用该 DeploymentRun ID 晋级", { status: source.status, environment: source.environment.kind }));
|
|
979
1176
|
return diagnostics;
|
|
980
1177
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1178
|
+
let version;
|
|
1179
|
+
try {
|
|
1180
|
+
const capabilities = await client.capabilities();
|
|
1181
|
+
const { assertApplicationContractCompatible, assertProductionPromotionPreflight } = await import('./deployment.js');
|
|
1182
|
+
assertApplicationContractCompatible(capabilities, CURRENT_APPLICATION_CONTRACT);
|
|
1183
|
+
version = await operationStage('promotion-preflight', '按原测试制品预检生产环境', () => client.productionPromotionPreflight(workspace.config.app.code, source.id));
|
|
1184
|
+
assertProductionPromotionPreflight(version, {
|
|
1185
|
+
appCode: workspace.config.app.code, sourceDeploymentId: source.id, appVersionId, packageDigest: source.packageDigest,
|
|
1186
|
+
});
|
|
1187
|
+
const testedLifecycle = lifecycleAtCommit(workspace.root, version.source.commit, this.appSpecContractIndex(workspace, true));
|
|
1188
|
+
if (!testedLifecycle.changeId || !testedLifecycle.acceptanceIds.length) {
|
|
1189
|
+
diagnostics.push(this.diagnostic('APPSPEC_TESTED_PLAN_REQUIRED', '指定测试版本缺少可追溯的需求与验收计划', 'appspec', '补齐真实记录并发布新的测试候选,再执行验收和生产晋级'));
|
|
1190
|
+
}
|
|
1191
|
+
// 原测试提交中的设计基线也必须有效,当前主线的补写不能补签旧版本。
|
|
1192
|
+
diagnostics.push(...testedLifecycle.design.diagnostics);
|
|
1193
|
+
const acceptance = await operationStage('business-acceptance', '核对原测试版本的业务验收报告', async () => verifyBusinessAcceptance(workspace.root, source, testedLifecycle));
|
|
1194
|
+
diagnostics.push(...acceptance.diagnostics);
|
|
1195
|
+
}
|
|
1196
|
+
catch (error) {
|
|
1197
|
+
return [this.configurationCompatibilityDiagnostic(error)];
|
|
987
1198
|
}
|
|
988
1199
|
if (workspace.context.workspace.dirty || version.source.dirty) {
|
|
989
|
-
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "
|
|
1200
|
+
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "提交并推送当前改动,或在干净主线目录中晋级该测试版本"));
|
|
990
1201
|
}
|
|
991
1202
|
const repository = git(workspace.root, ["config", "--get", "remote.origin.url"]);
|
|
992
1203
|
const currentRepository = this.repositoryIdentity(repository);
|
|
@@ -996,25 +1207,15 @@ export class OpenXiangdaApplicationServices {
|
|
|
996
1207
|
currentRepository !== sourceRepository) {
|
|
997
1208
|
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_REPOSITORY_MISMATCH", "当前工作区仓库与 AppVersion 来源仓库不一致", "git.remote.origin.url", "在构建该 AppVersion 的同一仓库中执行晋级", { current: repository || null, source: version.source.repository }));
|
|
998
1209
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
"@{upstream}",
|
|
1004
|
-
]);
|
|
1005
|
-
if (!upstream) {
|
|
1006
|
-
diagnostics.push(this.diagnostic("PRODUCTION_UPSTREAM_MISSING", "当前 Git 分支没有 upstream,无法证明 AppVersion 来源已进入远端主线", "git", "为当前分支配置 upstream 并推送来源 commit"));
|
|
1210
|
+
if (diagnostics.length)
|
|
1211
|
+
return diagnostics;
|
|
1212
|
+
try {
|
|
1213
|
+
await operationStage('promotion-source', '核对测试制品的主线来源', () => publishedDeliverySource(workspace.root, version.source.commit));
|
|
1007
1214
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
return diagnostics;
|
|
1013
|
-
}
|
|
1014
|
-
const contained = spawnSync("git", ["-C", workspace.root, "merge-base", "--is-ancestor", version.source.commit, upstream], { encoding: "utf8" });
|
|
1015
|
-
if (contained.status !== 0) {
|
|
1016
|
-
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_NOT_IN_UPSTREAM", "AppVersion 来源 commit 尚未包含在当前 upstream 中", "git", "推送来源 commit,确认 upstream 更新后再晋级", { commit: version.source.commit, upstream }));
|
|
1017
|
-
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
if (!(error instanceof DeliverySourceError))
|
|
1217
|
+
throw error;
|
|
1218
|
+
diagnostics.push(this.diagnostic(error.code, error.message, 'git', String(error.data.remediation), error.data));
|
|
1018
1219
|
}
|
|
1019
1220
|
return diagnostics;
|
|
1020
1221
|
}
|
|
@@ -1083,10 +1284,14 @@ export class OpenXiangdaApplicationServices {
|
|
|
1083
1284
|
if (!generated.ok) {
|
|
1084
1285
|
return this.result("dev", workspace.context.workspace, undefined, generated.diagnostics, generated.nextActions);
|
|
1085
1286
|
}
|
|
1287
|
+
const backendDiagnostics = this.initializeBackend(workspace);
|
|
1288
|
+
if (backendDiagnostics.length) {
|
|
1289
|
+
return this.result("dev", workspace.context.workspace, undefined, backendDiagnostics);
|
|
1290
|
+
}
|
|
1086
1291
|
try {
|
|
1087
1292
|
const linkPath = join(workspace.root, ".openxiangda", "link.json");
|
|
1088
1293
|
if (!existsSync(linkPath)) {
|
|
1089
|
-
throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory>");
|
|
1294
|
+
throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory> --base-url <platform>");
|
|
1090
1295
|
}
|
|
1091
1296
|
let link;
|
|
1092
1297
|
try {
|
|
@@ -1200,10 +1405,10 @@ export class OpenXiangdaApplicationServices {
|
|
|
1200
1405
|
: {}),
|
|
1201
1406
|
};
|
|
1202
1407
|
}
|
|
1203
|
-
async validateTargetConfigurationCompatibility(workspace, environmentKey) {
|
|
1408
|
+
async validateTargetConfigurationCompatibility(workspace, environmentKey, local) {
|
|
1204
1409
|
const client = await this.client(workspace.root);
|
|
1205
1410
|
const capabilities = await client.capabilities();
|
|
1206
|
-
const sources =
|
|
1411
|
+
const { sources } = local;
|
|
1207
1412
|
const required = {
|
|
1208
1413
|
...CURRENT_APPLICATION_CONTRACT,
|
|
1209
1414
|
configurationBundleSchemaVersion: sources.config.value.schemaVersion,
|
|
@@ -1233,9 +1438,21 @@ export class OpenXiangdaApplicationServices {
|
|
|
1233
1438
|
configurationDigest: sources.config.digest,
|
|
1234
1439
|
contractDigest: sources.contracts.digest,
|
|
1235
1440
|
}, requiredPlatformCapabilities(workspace.config));
|
|
1441
|
+
if (result.projectionDigest !== local.compiled.aggregateDigest) {
|
|
1442
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_CONFIGURATION_PROJECTION_MISMATCH', '相同校验规则与输入生成了不同投影,已停止构建,请检查平台配套版本', {
|
|
1443
|
+
pointer: '/projectionDigest', expectedProjectionDigest: local.compiled.aggregateDigest,
|
|
1444
|
+
actualProjectionDigest: result.projectionDigest, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1236
1447
|
return result;
|
|
1237
1448
|
}
|
|
1238
1449
|
configurationCompatibilityDiagnostic(error) {
|
|
1450
|
+
if (error instanceof NativeConfigurationCompilerError) {
|
|
1451
|
+
return this.diagnostic(error.code, '应用声明未通过本地完整配置校验', error.pointer, '按错误指针修正应用声明,运行 openxiangda check 复核', {
|
|
1452
|
+
pointer: error.pointer, identifiers: error.identifiers, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
|
|
1453
|
+
validationOwner: 'local-shared-compiler',
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1239
1456
|
const data = error instanceof ControlPlaneError &&
|
|
1240
1457
|
error.data &&
|
|
1241
1458
|
typeof error.data === "object" &&
|
|
@@ -1306,20 +1523,40 @@ export class OpenXiangdaApplicationServices {
|
|
|
1306
1523
|
get toolchainVersion() {
|
|
1307
1524
|
return this.options.toolchainVersion || OPENXIANGDA_TOOLCHAIN_VERSION;
|
|
1308
1525
|
}
|
|
1526
|
+
deliveryCacheContext(workspace) {
|
|
1527
|
+
return {
|
|
1528
|
+
root: workspace.root, frontendRoot: workspace.config.frontend.root,
|
|
1529
|
+
...(backendRuntimeRequired(workspace.config) ? { backendRoot: workspace.config.backend.root } : {}),
|
|
1530
|
+
toolchain: { version: this.toolchainVersion, capsule: this.toolchainCapsule, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST },
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1309
1533
|
get toolchainCapsule() {
|
|
1310
1534
|
return this.options.toolchainCapsule || defaultDevkitCoreToolchainCapsule();
|
|
1311
1535
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1536
|
+
initializeBackend(workspace) {
|
|
1537
|
+
try {
|
|
1538
|
+
initializeOptionalBackend(workspace.root, workspace.config, {
|
|
1539
|
+
...(this.options.toolchainCapsule?.packages.openxiangda
|
|
1540
|
+
? { sdkVersion: this.options.toolchainCapsule.packages.openxiangda }
|
|
1541
|
+
: {}),
|
|
1542
|
+
});
|
|
1543
|
+
return [];
|
|
1544
|
+
}
|
|
1545
|
+
catch (error) {
|
|
1546
|
+
return [{
|
|
1547
|
+
...this.diagnostic(String(error.code || "OPENXIANGDA_BACKEND_INITIALIZATION_FAILED"), error.message, workspace.config.backend.root, "修复初始化或依赖安装问题后重新运行 openxiangda check"),
|
|
1548
|
+
retryable: error.retryable === true,
|
|
1549
|
+
}];
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
packageScriptLabel(script) {
|
|
1553
|
+
return { check: '类型与规范检查', test: '应用测试', build: '前后端构建' }[script] || script;
|
|
1554
|
+
}
|
|
1555
|
+
async runPackageScript(root, script) {
|
|
1556
|
+
return operationStage(script, this.packageScriptLabel(script), async () => {
|
|
1557
|
+
const result = await runCommandProcess('pnpm', ['run', script], { cwd: root });
|
|
1558
|
+
return { ok: result.ok, exitCode: result.status ?? 1, output: result.output.trim(), outputTruncated: result.outputTruncated };
|
|
1317
1559
|
});
|
|
1318
|
-
return {
|
|
1319
|
-
ok: result.status === 0,
|
|
1320
|
-
exitCode: result.status ?? 1,
|
|
1321
|
-
output: `${result.stdout || ""}${result.stderr || ""}`.trim(),
|
|
1322
|
-
};
|
|
1323
1560
|
}
|
|
1324
1561
|
repositoryIdentity(value) {
|
|
1325
1562
|
const normalized = String(value || "").trim();
|
|
@@ -1513,7 +1750,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
1513
1750
|
remediation: "运行 create 幂等完成工作区绑定和平台初始化",
|
|
1514
1751
|
actionCode: "create",
|
|
1515
1752
|
actionLabel: "初始化应用",
|
|
1516
|
-
command: "openxiangda create <directory>",
|
|
1753
|
+
command: "openxiangda create <directory> --base-url <platform>",
|
|
1517
1754
|
path: ".openxiangda/link.json",
|
|
1518
1755
|
},
|
|
1519
1756
|
OPENXIANGDA_CONNECTED_LINK_INVALID: {
|
|
@@ -1521,7 +1758,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
1521
1758
|
remediation: "运行 create 幂等校验并重新生成当前应用绑定",
|
|
1522
1759
|
actionCode: "create",
|
|
1523
1760
|
actionLabel: "重新初始化应用",
|
|
1524
|
-
command: "openxiangda create <directory>",
|
|
1761
|
+
command: "openxiangda create <directory> --base-url <platform>",
|
|
1525
1762
|
path: ".openxiangda/link.json",
|
|
1526
1763
|
},
|
|
1527
1764
|
OPENXIANGDA_CONNECTED_LOGIN_REQUIRED: {
|
|
@@ -1595,7 +1832,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
1595
1832
|
operation,
|
|
1596
1833
|
workspace,
|
|
1597
1834
|
...(data === undefined ? {} : { data }),
|
|
1598
|
-
diagnostics,
|
|
1835
|
+
diagnostics: [...diagnostics].sort((left, right) => ({ error: 0, warning: 1, info: 2 })[left.severity] - ({ error: 0, warning: 1, info: 2 })[right.severity]),
|
|
1599
1836
|
nextActions,
|
|
1600
1837
|
};
|
|
1601
1838
|
}
|