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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -9
  3. package/dist/app-spec.d.ts +30 -12
  4. package/dist/app-spec.d.ts.map +1 -1
  5. package/dist/app-spec.js +238 -57
  6. package/dist/app-spec.js.map +1 -1
  7. package/dist/application-services.d.ts +625 -42
  8. package/dist/application-services.d.ts.map +1 -1
  9. package/dist/application-services.js +321 -85
  10. package/dist/application-services.js.map +1 -1
  11. package/dist/application-ui-contract.d.ts +5 -0
  12. package/dist/application-ui-contract.d.ts.map +1 -0
  13. package/dist/application-ui-contract.js +149 -0
  14. package/dist/application-ui-contract.js.map +1 -0
  15. package/dist/backend-image-build.js +6 -11
  16. package/dist/backend-image-build.js.map +1 -1
  17. package/dist/command-process.d.ts +14 -0
  18. package/dist/command-process.d.ts.map +1 -0
  19. package/dist/command-process.js +51 -0
  20. package/dist/command-process.js.map +1 -0
  21. package/dist/command-registry.d.ts +44 -4
  22. package/dist/command-registry.d.ts.map +1 -1
  23. package/dist/command-registry.js +10 -6
  24. package/dist/command-registry.js.map +1 -1
  25. package/dist/compiler/ai-catalog.d.ts.map +1 -1
  26. package/dist/compiler/ai-catalog.js +2 -1
  27. package/dist/compiler/ai-catalog.js.map +1 -1
  28. package/dist/compiler/application-model.d.ts +60 -0
  29. package/dist/compiler/application-model.d.ts.map +1 -0
  30. package/dist/compiler/application-model.js +141 -0
  31. package/dist/compiler/application-model.js.map +1 -0
  32. package/dist/compiler/bundle.d.ts.map +1 -1
  33. package/dist/compiler/bundle.js +196 -98
  34. package/dist/compiler/bundle.js.map +1 -1
  35. package/dist/compiler/config.d.ts +36 -10
  36. package/dist/compiler/config.d.ts.map +1 -1
  37. package/dist/compiler/config.js +536 -171
  38. package/dist/compiler/config.js.map +1 -1
  39. package/dist/compiler/field-surface.js +1 -1
  40. package/dist/compiler/field-surface.js.map +1 -1
  41. package/dist/compiler/package-compiler.d.ts.map +1 -1
  42. package/dist/compiler/package-compiler.js +12 -0
  43. package/dist/compiler/package-compiler.js.map +1 -1
  44. package/dist/compiler/permission-review.d.ts +139 -0
  45. package/dist/compiler/permission-review.d.ts.map +1 -0
  46. package/dist/compiler/permission-review.js +131 -0
  47. package/dist/compiler/permission-review.js.map +1 -0
  48. package/dist/configuration-preflight.d.ts +7 -0
  49. package/dist/configuration-preflight.d.ts.map +1 -0
  50. package/dist/configuration-preflight.js +13 -0
  51. package/dist/configuration-preflight.js.map +1 -0
  52. package/dist/control-plane-client.d.ts +56 -61
  53. package/dist/control-plane-client.d.ts.map +1 -1
  54. package/dist/control-plane-client.js +89 -8
  55. package/dist/control-plane-client.js.map +1 -1
  56. package/dist/delivery-cache.d.ts +34 -0
  57. package/dist/delivery-cache.d.ts.map +1 -0
  58. package/dist/delivery-cache.js +186 -0
  59. package/dist/delivery-cache.js.map +1 -0
  60. package/dist/delivery-source.d.ts +17 -0
  61. package/dist/delivery-source.d.ts.map +1 -0
  62. package/dist/delivery-source.js +85 -0
  63. package/dist/delivery-source.js.map +1 -0
  64. package/dist/deployment-observer.d.ts +14 -0
  65. package/dist/deployment-observer.d.ts.map +1 -0
  66. package/dist/deployment-observer.js +68 -0
  67. package/dist/deployment-observer.js.map +1 -0
  68. package/dist/deployment.d.ts +17 -1
  69. package/dist/deployment.d.ts.map +1 -1
  70. package/dist/deployment.js +55 -15
  71. package/dist/deployment.js.map +1 -1
  72. package/dist/design-readiness.d.ts +24 -0
  73. package/dist/design-readiness.d.ts.map +1 -0
  74. package/dist/design-readiness.js +102 -0
  75. package/dist/design-readiness.js.map +1 -0
  76. package/dist/developer-errors.d.ts +11 -0
  77. package/dist/developer-errors.d.ts.map +1 -0
  78. package/dist/developer-errors.js +16 -0
  79. package/dist/developer-errors.js.map +1 -0
  80. package/dist/developer-operations.d.ts +190 -0
  81. package/dist/developer-operations.d.ts.map +1 -0
  82. package/dist/developer-operations.js +65 -0
  83. package/dist/developer-operations.js.map +1 -0
  84. package/dist/development-lifecycle.d.ts +75 -0
  85. package/dist/development-lifecycle.d.ts.map +1 -0
  86. package/dist/development-lifecycle.js +189 -0
  87. package/dist/development-lifecycle.js.map +1 -0
  88. package/dist/documentation.d.ts +111 -0
  89. package/dist/documentation.d.ts.map +1 -0
  90. package/dist/documentation.js +112 -0
  91. package/dist/documentation.js.map +1 -0
  92. package/dist/index.d.ts +10 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +10 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/operation-progress.d.ts +26 -0
  97. package/dist/operation-progress.d.ts.map +1 -0
  98. package/dist/operation-progress.js +92 -0
  99. package/dist/operation-progress.js.map +1 -0
  100. package/dist/optional-backend.d.ts +14 -0
  101. package/dist/optional-backend.d.ts.map +1 -0
  102. package/dist/optional-backend.js +145 -0
  103. package/dist/optional-backend.js.map +1 -0
  104. package/dist/session.d.ts +22 -0
  105. package/dist/session.d.ts.map +1 -1
  106. package/dist/session.js +36 -0
  107. package/dist/session.js.map +1 -1
  108. package/dist/workspace-loader.d.ts.map +1 -1
  109. package/dist/workspace-loader.js +17 -2
  110. package/dist/workspace-loader.js.map +1 -1
  111. package/dist/workspace-operation.d.ts +3 -0
  112. package/dist/workspace-operation.d.ts.map +1 -0
  113. package/dist/workspace-operation.js +45 -0
  114. package/dist/workspace-operation.js.map +1 -0
  115. package/dist/workspace.d.ts.map +1 -1
  116. package/dist/workspace.js +1 -0
  117. package/dist/workspace.js.map +1 -1
  118. package/package.json +18 -4
  119. package/templates/backend/Dockerfile +20 -0
  120. package/templates/backend/package.json +27 -0
  121. package/templates/backend/src/app.module.ts +23 -0
  122. package/templates/backend/src/main.ts +5 -0
  123. 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
- return this.ok("workspace.context", workspace.context.workspace, workspace.context);
33
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
34
+ return this.ok("workspace.context", workspace.context.workspace, { ...workspace.context, development: {
35
+ ...summarizeLifecycle(lifecycle),
36
+ application: lifecycle.context.application ? { path: lifecycle.context.application.path, content: lifecycle.context.application.content } : null,
37
+ records: [
38
+ ...lifecycle.context.index.capabilities, ...lifecycle.context.index.activeChanges,
39
+ ...lifecycle.context.index.decisions, ...lifecycle.context.index.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
- return this.result("spec.check", workspace.context.workspace, context, context.diagnostics, [
102
+ const lifecycle = developmentLifecycle(workspace.root, this.appSpecContractIndex(workspace, true));
103
+ return this.result("spec.check", workspace.context.workspace, { ...context, lifecycle: summarizeLifecycle(lifecycle) }, lifecycle.diagnostics, [
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
- const generated = await this.generate({ root: workspace.root });
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
- let compatibility = null;
544
- if (environmentKey) {
545
- try {
546
- compatibility = await this.validateTargetConfigurationCompatibility(workspace, environmentKey);
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
- catch (error) {
549
- diagnostics.push(this.configurationCompatibilityDiagnostic(error));
550
- const sealedArtifact = this.writeCheckSealedArtifactStatus(workspace, false);
551
- return this.result("check", workspace.context.workspace, {
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 stages = ["check", "test", "build"].map(name => ({
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 command = this.runPackageScript(workspace.root, "build");
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 target = !hasBackend || input.backendImage
795
- ? undefined
796
- : backendImageBuildTarget(capabilities, workspace.config.app.code);
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
- const backendImage = hasBackend
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: 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
- const built = await this.buildSealed({
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
- const versions = await client.appVersions(workspace.config.app.code, 100);
982
- const version = versions.items.find(item => item.id === appVersionId);
983
- if (!version) {
984
- return [
985
- this.diagnostic("PRODUCTION_APP_VERSION_NOT_FOUND", "预发运行引用的 AppVersion 不在当前应用版本集中", "appVersionId"),
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", "提交并推送变更后重新部署预发,再晋级该 AppVersion"));
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
- const upstream = git(workspace.root, [
1000
- "rev-parse",
1001
- "--abbrev-ref",
1002
- "--symbolic-full-name",
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
- else {
1009
- const refreshed = spawnSync("git", ["-C", workspace.root, "fetch", "--quiet"], { encoding: "utf8" });
1010
- if (refreshed.status !== 0) {
1011
- diagnostics.push(this.diagnostic("PRODUCTION_UPSTREAM_REFRESH_FAILED", "无法刷新远端引用,生产晋级不能依赖可能过期的本地 upstream", "git", "恢复远端访问并重新执行晋级"));
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 = compileApplicationSources(workspace.config, this.toolchainVersion);
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
- runPackageScript(root, script) {
1313
- const result = spawnSync("pnpm", ["run", script], {
1314
- cwd: root,
1315
- encoding: "utf8",
1316
- env: process.env,
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
  }