openxiangda-devkit-core 2.0.0-alpha.97 → 2.0.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 +321 -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.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 +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 +56 -61
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +89 -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,77 @@ 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),
|
|
807
960
|
...(workspace.context.workspace.revision
|
|
808
961
|
? { sourceRevision: workspace.context.workspace.revision }
|
|
809
962
|
: {}),
|
|
810
|
-
})).reference
|
|
963
|
+
}))).reference
|
|
811
964
|
: undefined;
|
|
812
|
-
|
|
965
|
+
await assertDeliverySourceUnchanged(workspace.root, source);
|
|
966
|
+
const built = reused ? this.ok('build', workspace.context.workspace, {
|
|
967
|
+
...reused, sealedArtifact: this.sealedArtifactStatus(workspace, reused.package.digest, true),
|
|
968
|
+
}) : await operationStage('seal', '校验并冻结不可变制品', () => this.buildSealed({
|
|
813
969
|
...input,
|
|
814
970
|
root: workspace.root,
|
|
815
971
|
...(backendImage ? { backendImage } : {}),
|
|
816
972
|
skipWorkspaceBuild: true,
|
|
817
|
-
});
|
|
973
|
+
}));
|
|
818
974
|
if (!built.ok || !built.data)
|
|
819
975
|
return built;
|
|
976
|
+
if (preparedInputDigest && preparedInputDigest !== deliveryInputDigest(cache)) {
|
|
977
|
+
throw new DeliverySourceError('OPENXIANGDA_VALIDATION_INPUT_CHANGED', '密封期间源码、依赖或构建环境发生变化,已停止上传', '等待修改结束后重新运行 openxiangda deploy');
|
|
978
|
+
}
|
|
979
|
+
if (!reused)
|
|
980
|
+
recordSealedCandidate(cache, built.data.package);
|
|
981
|
+
const retained = await publishedDeliverySource(workspace.root, source.commit);
|
|
982
|
+
if (retained.repository !== source.repository) {
|
|
983
|
+
throw new DeliverySourceError('DELIVERY_REPOSITORY_CHANGED', '发布准备期间绑定远端发生变化', '恢复并核对原来源仓库,再发布同一候选');
|
|
984
|
+
}
|
|
985
|
+
await assertDeliverySourceUnchanged(workspace.root, source);
|
|
820
986
|
const deployment = await (await import("./deployment.js")).submitAppPackage({
|
|
821
987
|
client,
|
|
822
988
|
capabilities,
|
|
823
989
|
compiledPackage: built.data.package,
|
|
824
990
|
artifactContent: built.data.artifactContent,
|
|
825
991
|
environmentKind: input.environment,
|
|
992
|
+
...(input.deploymentStrategy ? { deploymentStrategy: input.deploymentStrategy } : {}),
|
|
826
993
|
...(input.environmentId ? { environmentId: input.environmentId } : {}),
|
|
827
994
|
idempotencyKey: input.idempotencyKey ||
|
|
828
|
-
`deploy:${built.data.package.digest}:${input.environment}`,
|
|
995
|
+
`deploy:${built.data.package.digest}:${input.environment}${input.deploymentStrategy === 'maintenance-replace' ? ':maintenance-replace' : ''}`,
|
|
829
996
|
...(input.requestId ? { requestId: input.requestId } : {}),
|
|
830
997
|
});
|
|
831
998
|
return this.ok("deploy", workspace.context.workspace, deployment, [
|
|
@@ -836,6 +1003,21 @@ export class OpenXiangdaApplicationServices {
|
|
|
836
1003
|
},
|
|
837
1004
|
]);
|
|
838
1005
|
}
|
|
1006
|
+
async runtimeCapacityPlan(workspace, client, capabilities, input, packageDigest) {
|
|
1007
|
+
const result = await operationStage('runtime-capacity', '只读预检运行资源配额', () => client.runtimeCapacityPreflight(workspace.config.app.code, capabilities, {
|
|
1008
|
+
schemaVersion: RUNTIME_CAPACITY_PREFLIGHT_SCHEMA,
|
|
1009
|
+
...(input.deploymentStrategy ? { deploymentStrategy: input.deploymentStrategy } : {}),
|
|
1010
|
+
environmentKey: input.environment,
|
|
1011
|
+
...(input.environmentId ? { environmentId: input.environmentId } : {}),
|
|
1012
|
+
backend: backendRuntimeRequired(workspace.config) ? { isolation: workspace.config.backend.isolation || 'shared', resourceProfile: workspace.config.backend.resourceProfile || 'light' } : null,
|
|
1013
|
+
...(packageDigest ? { packageDigest, idempotencyKey: input.idempotencyKey || `deploy:${packageDigest}:${input.environment}${input.deploymentStrategy === 'maintenance-replace' ? ':maintenance-replace' : ''}` } : {}),
|
|
1014
|
+
}));
|
|
1015
|
+
const { assertRuntimeCapacityPreflight } = await import('./deployment.js');
|
|
1016
|
+
assertRuntimeCapacityPreflight(result, input.environmentId, input.deploymentStrategy);
|
|
1017
|
+
if (result.basis === 'existing-run' && !packageDigest)
|
|
1018
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_RUNTIME_CAPACITY_PREFLIGHT_RESULT_INVALID', '平台返回了未绑定本地候选的原运行');
|
|
1019
|
+
return result;
|
|
1020
|
+
}
|
|
839
1021
|
async deploymentStatus(root, deploymentId) {
|
|
840
1022
|
const workspace = await this.workspace(root);
|
|
841
1023
|
const client = await this.client(workspace.root);
|
|
@@ -945,6 +1127,20 @@ export class OpenXiangdaApplicationServices {
|
|
|
945
1127
|
: []),
|
|
946
1128
|
];
|
|
947
1129
|
}
|
|
1130
|
+
async productionDeploymentPlan(root, deploymentId) {
|
|
1131
|
+
const workspace = await this.workspace(root);
|
|
1132
|
+
const client = await this.client(workspace.root);
|
|
1133
|
+
const source = await client.deployment(workspace.config.app.code, deploymentId);
|
|
1134
|
+
const appVersionId = String(source.result?.applicationVersionId || '');
|
|
1135
|
+
if (!appVersionId)
|
|
1136
|
+
throw new Error('DEPLOYMENT_APP_VERSION_NOT_AVAILABLE: 测试运行尚无可晋级版本');
|
|
1137
|
+
const diagnostics = await this.productionPromotionDiagnostics(workspace, client, source, appVersionId);
|
|
1138
|
+
return this.result('deployment.plan', workspace.context.workspace, {
|
|
1139
|
+
environment: 'production', sourceDeploymentId: deploymentId, appVersionId,
|
|
1140
|
+
packageDigest: source.packageDigest, sealed: true, submitted: false,
|
|
1141
|
+
rebuild: false, businessAcceptance: '需要核对该测试版本的业务验收记录',
|
|
1142
|
+
}, diagnostics);
|
|
1143
|
+
}
|
|
948
1144
|
async deployProduction(root, deploymentId) {
|
|
949
1145
|
const workspace = await this.workspace(root);
|
|
950
1146
|
const capsuleDiagnostics = this.toolchainDiagnostics(workspace);
|
|
@@ -961,12 +1157,12 @@ export class OpenXiangdaApplicationServices {
|
|
|
961
1157
|
if (diagnostics.length > 0) {
|
|
962
1158
|
return this.result("deploy", workspace.context.workspace, undefined, diagnostics);
|
|
963
1159
|
}
|
|
964
|
-
const deployment = await client.promote({
|
|
1160
|
+
const deployment = await operationStage('submit', '提交生产晋级', () => client.promote({
|
|
965
1161
|
appCode: workspace.config.app.code,
|
|
966
1162
|
appVersionId,
|
|
967
1163
|
environmentKind: environment,
|
|
968
1164
|
idempotencyKey: `promote:${source.packageDigest}:${environment}`,
|
|
969
|
-
});
|
|
1165
|
+
}));
|
|
970
1166
|
return this.ok("deploy", workspace.context.workspace, deployment, [
|
|
971
1167
|
{ code: "status", label: "查看生产部署状态", command: `openxiangda status ${deployment.id}` },
|
|
972
1168
|
]);
|
|
@@ -978,15 +1174,29 @@ export class OpenXiangdaApplicationServices {
|
|
|
978
1174
|
diagnostics.push(this.diagnostic("PRODUCTION_PREPRODUCTION_RUN_REQUIRED", "生产晋级来源必须是成功的预发 DeploymentRun", "deploymentId", "等待预发部署成功,并使用该 DeploymentRun ID 晋级", { status: source.status, environment: source.environment.kind }));
|
|
979
1175
|
return diagnostics;
|
|
980
1176
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1177
|
+
let version;
|
|
1178
|
+
try {
|
|
1179
|
+
const capabilities = await client.capabilities();
|
|
1180
|
+
const { assertApplicationContractCompatible, assertProductionPromotionPreflight } = await import('./deployment.js');
|
|
1181
|
+
assertApplicationContractCompatible(capabilities, CURRENT_APPLICATION_CONTRACT);
|
|
1182
|
+
version = await operationStage('promotion-preflight', '按原测试制品预检生产环境', () => client.productionPromotionPreflight(workspace.config.app.code, source.id));
|
|
1183
|
+
assertProductionPromotionPreflight(version, {
|
|
1184
|
+
appCode: workspace.config.app.code, sourceDeploymentId: source.id, appVersionId, packageDigest: source.packageDigest,
|
|
1185
|
+
});
|
|
1186
|
+
const testedLifecycle = lifecycleAtCommit(workspace.root, version.source.commit, this.appSpecContractIndex(workspace, true));
|
|
1187
|
+
if (!testedLifecycle.changeId || !testedLifecycle.acceptanceIds.length) {
|
|
1188
|
+
diagnostics.push(this.diagnostic('APPSPEC_TESTED_PLAN_REQUIRED', '指定测试版本缺少可追溯的需求与验收计划', 'appspec', '补齐真实记录并发布新的测试候选,再执行验收和生产晋级'));
|
|
1189
|
+
}
|
|
1190
|
+
// 原测试提交中的设计基线也必须有效,当前主线的补写不能补签旧版本。
|
|
1191
|
+
diagnostics.push(...testedLifecycle.design.diagnostics);
|
|
1192
|
+
const acceptance = await operationStage('business-acceptance', '核对原测试版本的业务验收报告', async () => verifyBusinessAcceptance(workspace.root, source, testedLifecycle));
|
|
1193
|
+
diagnostics.push(...acceptance.diagnostics);
|
|
1194
|
+
}
|
|
1195
|
+
catch (error) {
|
|
1196
|
+
return [this.configurationCompatibilityDiagnostic(error)];
|
|
987
1197
|
}
|
|
988
1198
|
if (workspace.context.workspace.dirty || version.source.dirty) {
|
|
989
|
-
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "
|
|
1199
|
+
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产晋级只接受来源干净且当前工作区无未提交变更的 AppVersion", "git", "提交并推送当前改动,或在干净主线目录中晋级该测试版本"));
|
|
990
1200
|
}
|
|
991
1201
|
const repository = git(workspace.root, ["config", "--get", "remote.origin.url"]);
|
|
992
1202
|
const currentRepository = this.repositoryIdentity(repository);
|
|
@@ -996,25 +1206,15 @@ export class OpenXiangdaApplicationServices {
|
|
|
996
1206
|
currentRepository !== sourceRepository) {
|
|
997
1207
|
diagnostics.push(this.diagnostic("PRODUCTION_SOURCE_REPOSITORY_MISMATCH", "当前工作区仓库与 AppVersion 来源仓库不一致", "git.remote.origin.url", "在构建该 AppVersion 的同一仓库中执行晋级", { current: repository || null, source: version.source.repository }));
|
|
998
1208
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
"@{upstream}",
|
|
1004
|
-
]);
|
|
1005
|
-
if (!upstream) {
|
|
1006
|
-
diagnostics.push(this.diagnostic("PRODUCTION_UPSTREAM_MISSING", "当前 Git 分支没有 upstream,无法证明 AppVersion 来源已进入远端主线", "git", "为当前分支配置 upstream 并推送来源 commit"));
|
|
1209
|
+
if (diagnostics.length)
|
|
1210
|
+
return diagnostics;
|
|
1211
|
+
try {
|
|
1212
|
+
await operationStage('promotion-source', '核对测试制品的主线来源', () => publishedDeliverySource(workspace.root, version.source.commit));
|
|
1007
1213
|
}
|
|
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
|
-
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
if (!(error instanceof DeliverySourceError))
|
|
1216
|
+
throw error;
|
|
1217
|
+
diagnostics.push(this.diagnostic(error.code, error.message, 'git', String(error.data.remediation), error.data));
|
|
1018
1218
|
}
|
|
1019
1219
|
return diagnostics;
|
|
1020
1220
|
}
|
|
@@ -1083,10 +1283,14 @@ export class OpenXiangdaApplicationServices {
|
|
|
1083
1283
|
if (!generated.ok) {
|
|
1084
1284
|
return this.result("dev", workspace.context.workspace, undefined, generated.diagnostics, generated.nextActions);
|
|
1085
1285
|
}
|
|
1286
|
+
const backendDiagnostics = this.initializeBackend(workspace);
|
|
1287
|
+
if (backendDiagnostics.length) {
|
|
1288
|
+
return this.result("dev", workspace.context.workspace, undefined, backendDiagnostics);
|
|
1289
|
+
}
|
|
1086
1290
|
try {
|
|
1087
1291
|
const linkPath = join(workspace.root, ".openxiangda", "link.json");
|
|
1088
1292
|
if (!existsSync(linkPath)) {
|
|
1089
|
-
throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory>");
|
|
1293
|
+
throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory> --base-url <platform>");
|
|
1090
1294
|
}
|
|
1091
1295
|
let link;
|
|
1092
1296
|
try {
|
|
@@ -1200,10 +1404,10 @@ export class OpenXiangdaApplicationServices {
|
|
|
1200
1404
|
: {}),
|
|
1201
1405
|
};
|
|
1202
1406
|
}
|
|
1203
|
-
async validateTargetConfigurationCompatibility(workspace, environmentKey) {
|
|
1407
|
+
async validateTargetConfigurationCompatibility(workspace, environmentKey, local) {
|
|
1204
1408
|
const client = await this.client(workspace.root);
|
|
1205
1409
|
const capabilities = await client.capabilities();
|
|
1206
|
-
const sources =
|
|
1410
|
+
const { sources } = local;
|
|
1207
1411
|
const required = {
|
|
1208
1412
|
...CURRENT_APPLICATION_CONTRACT,
|
|
1209
1413
|
configurationBundleSchemaVersion: sources.config.value.schemaVersion,
|
|
@@ -1233,9 +1437,21 @@ export class OpenXiangdaApplicationServices {
|
|
|
1233
1437
|
configurationDigest: sources.config.digest,
|
|
1234
1438
|
contractDigest: sources.contracts.digest,
|
|
1235
1439
|
}, requiredPlatformCapabilities(workspace.config));
|
|
1440
|
+
if (result.projectionDigest !== local.compiled.aggregateDigest) {
|
|
1441
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_CONFIGURATION_PROJECTION_MISMATCH', '相同校验规则与输入生成了不同投影,已停止构建,请检查平台配套版本', {
|
|
1442
|
+
pointer: '/projectionDigest', expectedProjectionDigest: local.compiled.aggregateDigest,
|
|
1443
|
+
actualProjectionDigest: result.projectionDigest, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1236
1446
|
return result;
|
|
1237
1447
|
}
|
|
1238
1448
|
configurationCompatibilityDiagnostic(error) {
|
|
1449
|
+
if (error instanceof NativeConfigurationCompilerError) {
|
|
1450
|
+
return this.diagnostic(error.code, '应用声明未通过本地完整配置校验', error.pointer, '按错误指针修正应用声明,运行 openxiangda check 复核', {
|
|
1451
|
+
pointer: error.pointer, identifiers: error.identifiers, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
|
|
1452
|
+
validationOwner: 'local-shared-compiler',
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1239
1455
|
const data = error instanceof ControlPlaneError &&
|
|
1240
1456
|
error.data &&
|
|
1241
1457
|
typeof error.data === "object" &&
|
|
@@ -1306,20 +1522,40 @@ export class OpenXiangdaApplicationServices {
|
|
|
1306
1522
|
get toolchainVersion() {
|
|
1307
1523
|
return this.options.toolchainVersion || OPENXIANGDA_TOOLCHAIN_VERSION;
|
|
1308
1524
|
}
|
|
1525
|
+
deliveryCacheContext(workspace) {
|
|
1526
|
+
return {
|
|
1527
|
+
root: workspace.root, frontendRoot: workspace.config.frontend.root,
|
|
1528
|
+
...(backendRuntimeRequired(workspace.config) ? { backendRoot: workspace.config.backend.root } : {}),
|
|
1529
|
+
toolchain: { version: this.toolchainVersion, capsule: this.toolchainCapsule, validatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST },
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1309
1532
|
get toolchainCapsule() {
|
|
1310
1533
|
return this.options.toolchainCapsule || defaultDevkitCoreToolchainCapsule();
|
|
1311
1534
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1535
|
+
initializeBackend(workspace) {
|
|
1536
|
+
try {
|
|
1537
|
+
initializeOptionalBackend(workspace.root, workspace.config, {
|
|
1538
|
+
...(this.options.toolchainCapsule?.packages.openxiangda
|
|
1539
|
+
? { sdkVersion: this.options.toolchainCapsule.packages.openxiangda }
|
|
1540
|
+
: {}),
|
|
1541
|
+
});
|
|
1542
|
+
return [];
|
|
1543
|
+
}
|
|
1544
|
+
catch (error) {
|
|
1545
|
+
return [{
|
|
1546
|
+
...this.diagnostic(String(error.code || "OPENXIANGDA_BACKEND_INITIALIZATION_FAILED"), error.message, workspace.config.backend.root, "修复初始化或依赖安装问题后重新运行 openxiangda check"),
|
|
1547
|
+
retryable: error.retryable === true,
|
|
1548
|
+
}];
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
packageScriptLabel(script) {
|
|
1552
|
+
return { check: '类型与规范检查', test: '应用测试', build: '前后端构建' }[script] || script;
|
|
1553
|
+
}
|
|
1554
|
+
async runPackageScript(root, script) {
|
|
1555
|
+
return operationStage(script, this.packageScriptLabel(script), async () => {
|
|
1556
|
+
const result = await runCommandProcess('pnpm', ['run', script], { cwd: root });
|
|
1557
|
+
return { ok: result.ok, exitCode: result.status ?? 1, output: result.output.trim(), outputTruncated: result.outputTruncated };
|
|
1317
1558
|
});
|
|
1318
|
-
return {
|
|
1319
|
-
ok: result.status === 0,
|
|
1320
|
-
exitCode: result.status ?? 1,
|
|
1321
|
-
output: `${result.stdout || ""}${result.stderr || ""}`.trim(),
|
|
1322
|
-
};
|
|
1323
1559
|
}
|
|
1324
1560
|
repositoryIdentity(value) {
|
|
1325
1561
|
const normalized = String(value || "").trim();
|
|
@@ -1513,7 +1749,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
1513
1749
|
remediation: "运行 create 幂等完成工作区绑定和平台初始化",
|
|
1514
1750
|
actionCode: "create",
|
|
1515
1751
|
actionLabel: "初始化应用",
|
|
1516
|
-
command: "openxiangda create <directory>",
|
|
1752
|
+
command: "openxiangda create <directory> --base-url <platform>",
|
|
1517
1753
|
path: ".openxiangda/link.json",
|
|
1518
1754
|
},
|
|
1519
1755
|
OPENXIANGDA_CONNECTED_LINK_INVALID: {
|
|
@@ -1521,7 +1757,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
1521
1757
|
remediation: "运行 create 幂等校验并重新生成当前应用绑定",
|
|
1522
1758
|
actionCode: "create",
|
|
1523
1759
|
actionLabel: "重新初始化应用",
|
|
1524
|
-
command: "openxiangda create <directory>",
|
|
1760
|
+
command: "openxiangda create <directory> --base-url <platform>",
|
|
1525
1761
|
path: ".openxiangda/link.json",
|
|
1526
1762
|
},
|
|
1527
1763
|
OPENXIANGDA_CONNECTED_LOGIN_REQUIRED: {
|
|
@@ -1595,7 +1831,7 @@ export class OpenXiangdaApplicationServices {
|
|
|
1595
1831
|
operation,
|
|
1596
1832
|
workspace,
|
|
1597
1833
|
...(data === undefined ? {} : { data }),
|
|
1598
|
-
diagnostics,
|
|
1834
|
+
diagnostics: [...diagnostics].sort((left, right) => ({ error: 0, warning: 1, info: 2 })[left.severity] - ({ error: 0, warning: 1, info: 2 })[right.severity]),
|
|
1599
1835
|
nextActions,
|
|
1600
1836
|
};
|
|
1601
1837
|
}
|