openxiangda-devkit-core 2.0.0-alpha.29 → 2.0.0-alpha.34

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 (69) hide show
  1. package/README.md +2 -1
  2. package/dist/application-services.d.ts +79 -43
  3. package/dist/application-services.d.ts.map +1 -1
  4. package/dist/application-services.js +257 -160
  5. package/dist/application-services.js.map +1 -1
  6. package/dist/backend-image-build.d.ts +23 -0
  7. package/dist/backend-image-build.d.ts.map +1 -0
  8. package/dist/backend-image-build.js +192 -0
  9. package/dist/backend-image-build.js.map +1 -0
  10. package/dist/command-registry.d.ts +26 -576
  11. package/dist/command-registry.d.ts.map +1 -1
  12. package/dist/command-registry.js +9 -374
  13. package/dist/command-registry.js.map +1 -1
  14. package/dist/compiler/bundle.d.ts +23 -0
  15. package/dist/compiler/bundle.d.ts.map +1 -0
  16. package/dist/compiler/bundle.js +604 -0
  17. package/dist/compiler/bundle.js.map +1 -0
  18. package/dist/compiler/config.d.ts +377 -0
  19. package/dist/compiler/config.d.ts.map +1 -0
  20. package/dist/compiler/config.js +1137 -0
  21. package/dist/compiler/config.js.map +1 -0
  22. package/dist/compiler/index.d.ts +4 -0
  23. package/dist/compiler/index.d.ts.map +1 -0
  24. package/dist/compiler/index.js +4 -0
  25. package/dist/compiler/index.js.map +1 -0
  26. package/dist/compiler/package-compiler.d.ts +22 -0
  27. package/dist/compiler/package-compiler.d.ts.map +1 -0
  28. package/dist/compiler/package-compiler.js +60 -0
  29. package/dist/compiler/package-compiler.js.map +1 -0
  30. package/dist/config.d.ts +1 -1
  31. package/dist/config.js +1 -1
  32. package/dist/connected-development.d.ts +62 -0
  33. package/dist/connected-development.d.ts.map +1 -0
  34. package/dist/connected-development.js +389 -0
  35. package/dist/connected-development.js.map +1 -0
  36. package/dist/control-plane-client.d.ts +50 -1
  37. package/dist/control-plane-client.d.ts.map +1 -1
  38. package/dist/control-plane-client.js +43 -2
  39. package/dist/control-plane-client.js.map +1 -1
  40. package/dist/deployment.d.ts +3 -1
  41. package/dist/deployment.d.ts.map +1 -1
  42. package/dist/deployment.js +18 -8
  43. package/dist/deployment.js.map +1 -1
  44. package/dist/index.d.ts +2 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +2 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/internal/workflow.d.ts +81 -0
  49. package/dist/internal/workflow.d.ts.map +1 -0
  50. package/dist/internal/workflow.js +351 -0
  51. package/dist/internal/workflow.js.map +1 -0
  52. package/dist/package-compiler.d.ts +1 -1
  53. package/dist/package-compiler.d.ts.map +1 -1
  54. package/dist/package-compiler.js +1 -1
  55. package/dist/package-compiler.js.map +1 -1
  56. package/dist/testing.d.ts +38 -0
  57. package/dist/testing.d.ts.map +1 -0
  58. package/dist/testing.js +46 -0
  59. package/dist/testing.js.map +1 -0
  60. package/dist/workspace-loader.d.ts +1 -1
  61. package/package.json +8 -4
  62. package/dist/local-development.d.ts +0 -85
  63. package/dist/local-development.d.ts.map +0 -1
  64. package/dist/local-development.js +0 -919
  65. package/dist/local-development.js.map +0 -1
  66. package/dist/local-postgres.d.ts +0 -32
  67. package/dist/local-postgres.d.ts.map +0 -1
  68. package/dist/local-postgres.js +0 -324
  69. package/dist/local-postgres.js.map +0 -1
@@ -2,10 +2,11 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from "node:fs";
3
3
  import { dirname, join, relative } from "node:path";
4
4
  import { SCHEMA_VERSIONS, canonicalJson, } from "openxiangda-contracts";
5
- import { compileApplicationSources, compileAppPackage, sha256Bytes, validateAppConfig, } from "openxiangda-compiler";
6
- import { OpenXiangdaControlPlaneClient, } from "./control-plane-client.js";
5
+ import { compileApplicationSources, compileAppPackage, requiredPlatformCapabilities, sha256Bytes, validateAppConfig, } from "./compiler/index.js";
6
+ import { OpenXiangdaControlPlaneClient, ControlPlaneError, } from "./control-plane-client.js";
7
7
  import { normalizePlatformBaseUrl, OpenXiangdaDeveloperSession, } from "./session.js";
8
- import { localDevelopmentStatus, localDevelopmentSession, resetLocalDevelopmentData, runLocalDevelopment, stopLocalDevelopment, } from "./local-development.js";
8
+ import { runConnectedDevelopment, selectConnectedDevelopmentEnvironment, } from "./connected-development.js";
9
+ import { backendImageBuildTarget, publishBackendImage, } from "./backend-image-build.js";
9
10
  import { OPENXIANGDA_TOOLCHAIN_VERSION } from "./version.js";
10
11
  import { git, loadWorkspace, } from "./workspace-loader.js";
11
12
  export class OpenXiangdaApplicationServices {
@@ -289,27 +290,6 @@ export class OpenXiangdaApplicationServices {
289
290
  const data = await (await this.client(workspace.root)).setTimerSubscriptionStatus(workspace.config.app.code, timerId, input);
290
291
  return this.ok("event.timer.status", workspace.context.workspace, data);
291
292
  }
292
- async fireLocalTimer(root, timerId) {
293
- const workspace = await this.workspace(root);
294
- const session = localDevelopmentSession(workspace.root);
295
- if (!session || session.status !== "ready" || session.uiOnly) {
296
- throw new Error("OPENXIANGDA_LOCAL_DEV_SESSION_REQUIRED");
297
- }
298
- const response = await fetch(`${session.urls.platform}/__openxiangda-local/timers/${encodeURIComponent(timerId)}/fire`, {
299
- method: "POST",
300
- headers: {
301
- Accept: "application/json",
302
- "X-OpenXiangda-Local-Control": "v1",
303
- },
304
- });
305
- const envelope = (await response.json());
306
- if (!response.ok || Number(envelope.code) >= 400) {
307
- throw new Error(String(envelope.errorCode ||
308
- envelope.message ||
309
- `OPENXIANGDA_LOCAL_TIMER_FIRE_FAILED:${response.status}`));
310
- }
311
- return this.ok("event.timer.fire-local", workspace.context.workspace, envelope.data);
312
- }
313
293
  async workflowAssigneeProviders(root, environmentKey) {
314
294
  const workspace = await this.workspace(root);
315
295
  const environment = this.deploymentEnvironment(environmentKey);
@@ -347,7 +327,7 @@ export class OpenXiangdaApplicationServices {
347
327
  const changed = current !== sources.contracts.typescript;
348
328
  const diagnostics = [];
349
329
  if (input.check && changed) {
350
- diagnostics.push(this.diagnostic("GENERATED_CONTRACTS_OUTDATED", "生成契约与应用声明不一致", relative(workspace.root, output), "运行 openxiangda generate 并提交生成结果"));
330
+ diagnostics.push(this.diagnostic("GENERATED_CONTRACTS_OUTDATED", "生成契约与应用声明不一致", relative(workspace.root, output), "运行 openxiangda check 并提交生成结果"));
351
331
  }
352
332
  else if (changed) {
353
333
  mkdirSync(dirname(output), { recursive: true });
@@ -362,7 +342,7 @@ export class OpenXiangdaApplicationServices {
362
342
  {
363
343
  code: "generate",
364
344
  label: "更新生成契约",
365
- command: "openxiangda generate",
345
+ command: "openxiangda check",
366
346
  },
367
347
  ]
368
348
  : []);
@@ -373,16 +353,17 @@ export class OpenXiangdaApplicationServices {
373
353
  ...validateAppConfig(workspace.config),
374
354
  ...this.versionTrainDiagnostics(workspace),
375
355
  ];
376
- const generated = await this.generate({
377
- root: workspace.root,
378
- check: true,
379
- });
356
+ const generated = await this.generate({ root: workspace.root });
380
357
  diagnostics.push(...generated.diagnostics);
381
- const command = this.runPackageScript(workspace.root, "check");
382
- if (!command.ok) {
383
- diagnostics.push(this.diagnostic("WORKSPACE_CHECK_FAILED", "工作区 check 脚本失败", "package.json#scripts.check", "修复类型、契约或静态检查错误后重试", { output: command.output }));
358
+ const stages = ["check", "test", "build"].map(name => ({
359
+ name,
360
+ ...this.runPackageScript(workspace.root, name),
361
+ }));
362
+ for (const stage of stages.filter(item => !item.ok)) {
363
+ const code = `WORKSPACE_${stage.name.toUpperCase()}_FAILED`;
364
+ diagnostics.push(this.diagnostic(code, `工作区 ${stage.name} 脚本失败`, `package.json#scripts.${stage.name}`, "修复失败后重新运行 openxiangda check", { output: stage.output }));
384
365
  }
385
- return this.result("check", workspace.context.workspace, { stages: [{ name: "workspace", ...command }] }, diagnostics);
366
+ return this.result("check", workspace.context.workspace, { generated: generated.data, stages }, diagnostics);
386
367
  }
387
368
  async test(root) {
388
369
  const workspace = await this.workspace(root);
@@ -408,6 +389,18 @@ export class OpenXiangdaApplicationServices {
408
389
  },
409
390
  };
410
391
  }
392
+ async buildPreview(root) {
393
+ const checked = await this.check(root);
394
+ return {
395
+ ...checked,
396
+ operation: "build.preview",
397
+ data: {
398
+ sealed: false,
399
+ packageDigest: null,
400
+ check: checked.data,
401
+ },
402
+ };
403
+ }
411
404
  async buildSealed(input = {}) {
412
405
  const workspace = await this.workspace(input.root);
413
406
  const diagnostics = [];
@@ -419,13 +412,13 @@ export class OpenXiangdaApplicationServices {
419
412
  return this.result("build", workspace.context.workspace, undefined, diagnostics);
420
413
  }
421
414
  }
422
- const backendImage = String(input.backendImage || process.env.OPENXIANGDA_BACKEND_IMAGE || "").trim();
415
+ const backendImage = String(input.backendImage || "").trim();
423
416
  if (!backendImage) {
424
- diagnostics.push(this.diagnostic("BACKEND_IMAGE_REQUIRED", "当前应用包含 NestJS 后端,密封 AppPackage 时必须提供不可变 OCI image", "backend.image", "通过 --backend-image <registry/repository@sha256:digest> 或 OPENXIANGDA_BACKEND_IMAGE 提供"));
417
+ diagnostics.push(this.diagnostic("BACKEND_IMAGE_REQUIRED", "当前应用包含 NestJS 后端,密封 AppPackage 时必须提供不可变 OCI image", "backend.image", "运行 openxiangda deploy 由工具自动构建并推送官方后端镜像"));
425
418
  return this.result("build", workspace.context.workspace, undefined, diagnostics);
426
419
  }
427
420
  if (!/@sha256:[a-f0-9]{64}$/i.test(backendImage)) {
428
- diagnostics.push(this.diagnostic("BACKEND_IMAGE_NOT_IMMUTABLE", "后端镜像必须使用不可变 sha256 digest,不能使用可变 tag", "backend.image", "将镜像引用改为 <registry/repository@sha256:64位摘要>"));
421
+ diagnostics.push(this.diagnostic("BACKEND_IMAGE_NOT_IMMUTABLE", "后端镜像必须使用不可变 sha256 digest,不能使用可变 tag", "backend.image", "运行 openxiangda deploy 重新构建并取得不可变镜像摘要"));
429
422
  return this.result("build", workspace.context.workspace, undefined, diagnostics);
430
423
  }
431
424
  const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
@@ -502,39 +495,63 @@ export class OpenXiangdaApplicationServices {
502
495
  {
503
496
  code: "deploy",
504
497
  label: "部署到预发",
505
- command: "openxiangda deploy preproduction",
498
+ command: "openxiangda deploy",
506
499
  },
507
500
  ]);
508
501
  }
509
502
  async deploymentPlan(input) {
510
- const built = await this.build({ ...input, write: false });
511
- if (!built.ok || !built.data)
512
- return built;
513
- return {
514
- ...built,
515
- operation: "deployment.plan",
516
- data: {
517
- environment: input.environment,
518
- packageDigest: built.data.package.digest,
519
- appCode: built.data.package.manifest.appCode,
520
- artifacts: built.data.package.manifest.artifacts,
521
- requiredCapabilities: built.data.package.manifest.compatibility.requiredCapabilities,
522
- },
523
- };
503
+ const workspace = await this.workspace(input.root);
504
+ const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
505
+ return this.ok("deployment.plan", workspace.context.workspace, {
506
+ environment: input.environment,
507
+ appCode: workspace.config.app.code,
508
+ sealed: false,
509
+ packageDigest: null,
510
+ buildOwner: "openxiangda deploy",
511
+ frontendRoot: workspace.config.frontend.root,
512
+ backendRoot: workspace.config.backend.root,
513
+ configDigest: sources.config.digest,
514
+ contractDigest: sources.contracts.digest,
515
+ });
524
516
  }
525
517
  async deploy(input) {
526
518
  const workspace = await this.workspace(input.root);
527
519
  if (String(input.environment) !== "preproduction") {
528
520
  return this.result("deploy", workspace.context.workspace, undefined, [
529
- this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy preproduction;生产环境必须晋级同一 AppVersion"),
521
+ this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy;生产环境必须显式复用成功的测试 DeploymentRun"),
530
522
  ]);
531
523
  }
532
- const built = await this.buildSealed({ ...input, root: workspace.root });
524
+ const client = await this.client(workspace.root);
525
+ const capabilities = await client.capabilities();
526
+ const { assertRequiredCapabilitiesAvailable } = await import("./deployment.js");
527
+ assertRequiredCapabilitiesAvailable(capabilities, requiredPlatformCapabilities(workspace.config));
528
+ const target = input.backendImage
529
+ ? undefined
530
+ : backendImageBuildTarget(capabilities, workspace.config.app.code);
531
+ const checked = await this.check(workspace.root);
532
+ if (!checked.ok)
533
+ return { ...checked, operation: "deploy" };
534
+ const backendImage = input.backendImage
535
+ ? input.backendImage
536
+ : (await publishBackendImage({
537
+ root: workspace.root,
538
+ backendRoot: workspace.config.backend.root,
539
+ target: target,
540
+ ...(workspace.context.workspace.revision
541
+ ? { sourceRevision: workspace.context.workspace.revision }
542
+ : {}),
543
+ })).reference;
544
+ const built = await this.buildSealed({
545
+ ...input,
546
+ root: workspace.root,
547
+ backendImage,
548
+ skipWorkspaceBuild: true,
549
+ });
533
550
  if (!built.ok || !built.data)
534
551
  return built;
535
- const client = await this.client(workspace.root);
536
552
  const deployment = await (await import("./deployment.js")).submitAppPackage({
537
553
  client,
554
+ capabilities,
538
555
  compiledPackage: built.data.package,
539
556
  artifactContent: built.data.artifactContent,
540
557
  environmentKind: input.environment,
@@ -556,7 +573,14 @@ export class OpenXiangdaApplicationServices {
556
573
  const client = await this.client(workspace.root);
557
574
  const data = deploymentId
558
575
  ? await client.deployment(workspace.config.app.code, deploymentId)
559
- : await client.deployments(workspace.config.app.code, 20);
576
+ : (await client.deployments(workspace.config.app.code, 1)).items[0];
577
+ if (!data) {
578
+ return this.result("status", workspace.context.workspace, undefined, [
579
+ this.diagnostic("DEPLOYMENT_NOT_FOUND", "当前应用还没有部署记录", "deploymentId", "运行 openxiangda deploy 创建测试部署"),
580
+ ], [
581
+ { code: "deploy", label: "部署测试环境", command: "openxiangda deploy" },
582
+ ]);
583
+ }
560
584
  return this.ok("status", workspace.context.workspace, data);
561
585
  }
562
586
  async environmentStatus(root) {
@@ -572,12 +596,14 @@ export class OpenXiangdaApplicationServices {
572
596
  }
573
597
  async deploymentLogs(root, deploymentId) {
574
598
  const status = await this.deploymentStatus(root, deploymentId);
599
+ if (!status.ok || !status.data)
600
+ return { ...status, operation: "logs" };
575
601
  const run = status.data;
576
602
  return {
577
603
  ...status,
578
604
  operation: "logs",
579
605
  data: {
580
- deploymentId,
606
+ deploymentId: run.id,
581
607
  status: run.status,
582
608
  stage: run.stage,
583
609
  checkpoints: run.checkpoints,
@@ -596,11 +622,9 @@ export class OpenXiangdaApplicationServices {
596
622
  const deployment = await (await this.client(workspace.root)).cancelDeployment(workspace.config.app.code, deploymentId);
597
623
  return this.ok("cancel", workspace.context.workspace, deployment);
598
624
  }
599
- async promote(root, deploymentId, environment) {
625
+ async deployProduction(root, deploymentId) {
600
626
  const workspace = await this.workspace(root);
601
- if (String(environment) !== "production") {
602
- throw new Error(`OPENXIANGDA_PROMOTION_ENVIRONMENT_INVALID: ${String(environment)}`);
603
- }
627
+ const environment = "production";
604
628
  const client = await this.client(workspace.root);
605
629
  const source = await client.deployment(workspace.config.app.code, deploymentId);
606
630
  const appVersionId = String(source.result?.applicationVersionId || "");
@@ -608,7 +632,7 @@ export class OpenXiangdaApplicationServices {
608
632
  throw new Error("DEPLOYMENT_APP_VERSION_NOT_AVAILABLE");
609
633
  const diagnostics = await this.productionPromotionDiagnostics(workspace, client, source, appVersionId);
610
634
  if (diagnostics.length > 0) {
611
- return this.result("promote", workspace.context.workspace, undefined, diagnostics);
635
+ return this.result("deploy", workspace.context.workspace, undefined, diagnostics);
612
636
  }
613
637
  const deployment = await client.promote({
614
638
  appCode: workspace.config.app.code,
@@ -616,7 +640,9 @@ export class OpenXiangdaApplicationServices {
616
640
  environmentKind: environment,
617
641
  idempotencyKey: `promote:${source.packageDigest}:${environment}`,
618
642
  });
619
- return this.ok("promote", workspace.context.workspace, deployment);
643
+ return this.ok("deploy", workspace.context.workspace, deployment, [
644
+ { code: "status", label: "查看生产部署状态", command: `openxiangda status ${deployment.id}` },
645
+ ]);
620
646
  }
621
647
  async productionPromotionDiagnostics(workspace, client, source, appVersionId) {
622
648
  const diagnostics = [];
@@ -704,63 +730,10 @@ export class OpenXiangdaApplicationServices {
704
730
  {
705
731
  code: "status",
706
732
  label: "查看环境状态",
707
- command: "openxiangda environment status",
733
+ command: "openxiangda status",
708
734
  },
709
735
  ]);
710
736
  }
711
- async doctor(root) {
712
- const workspace = await this.workspace(root);
713
- const commands = ["git", "node", "pnpm", "docker"].map((command) => {
714
- const result = spawnSync(command, ["--version"], { encoding: "utf8" });
715
- return {
716
- command,
717
- ok: result.status === 0,
718
- version: String(result.stdout || result.stderr || "").trim(),
719
- };
720
- });
721
- const dockerInfo = spawnSync("docker", ["info", "--format", "{{.ServerVersion}}"], { encoding: "utf8" });
722
- const containerRuntime = {
723
- requiredFor: "openxiangda dev",
724
- ok: dockerInfo.status === 0,
725
- serverVersion: String(dockerInfo.stdout || "").trim() || null,
726
- message: dockerInfo.status === 0
727
- ? null
728
- : String(dockerInfo.stderr || dockerInfo.stdout || "").trim() ||
729
- "Docker daemon is unavailable",
730
- };
731
- let platform;
732
- try {
733
- platform = (await (await this.client(workspace.root)).capabilities());
734
- }
735
- catch (error) {
736
- platform = { ok: false, message: error.message };
737
- }
738
- const diagnostics = commands
739
- .filter((item) => !item.ok)
740
- .map((item) => this.diagnostic("TOOLCHAIN_COMMAND_MISSING", `缺少必需命令: ${item.command}`, item.command));
741
- if (!containerRuntime.ok &&
742
- commands.find(item => item.command === "docker")?.ok) {
743
- diagnostics.push(this.diagnostic("LOCAL_CONTAINER_RUNTIME_UNAVAILABLE", "Docker 已安装但容器运行时不可用,完整本地开发无法启动 PostgreSQL", "docker.info", "启动 Docker Desktop(或兼容 Docker daemon)后重新执行 openxiangda doctor"));
744
- }
745
- return this.result("doctor", workspace.context.workspace, {
746
- commands,
747
- containerRuntime,
748
- localDevelopment: localDevelopmentStatus(workspace.root),
749
- platform,
750
- }, diagnostics);
751
- }
752
- async devStatus(root) {
753
- const workspace = await this.workspace(root);
754
- return this.ok("dev.status", workspace.context.workspace, localDevelopmentStatus(workspace.root));
755
- }
756
- async devStop(root) {
757
- const workspace = await this.workspace(root);
758
- return this.ok("dev.stop", workspace.context.workspace, await stopLocalDevelopment(workspace.root));
759
- }
760
- async devResetData(root) {
761
- const workspace = await this.workspace(root);
762
- return this.ok("dev.reset", workspace.context.workspace, await resetLocalDevelopmentData(workspace.root));
763
- }
764
737
  async dev(root, input = {}) {
765
738
  const workspace = await this.workspace(root);
766
739
  const generated = await this.generate({
@@ -769,53 +742,101 @@ export class OpenXiangdaApplicationServices {
769
742
  if (!generated.ok) {
770
743
  return this.result("dev", workspace.context.workspace, undefined, generated.diagnostics, generated.nextActions);
771
744
  }
772
- const localFixturePath = join(workspace.root, "openxiangda.local.json");
773
- let localFixture;
774
- if (existsSync(localFixturePath)) {
745
+ try {
746
+ const linkPath = join(workspace.root, ".openxiangda", "link.json");
747
+ if (!existsSync(linkPath)) {
748
+ throw new Error("OPENXIANGDA_CONNECTED_LINK_REQUIRED: 先运行 openxiangda create <directory>");
749
+ }
750
+ let link;
775
751
  try {
776
- localFixture = JSON.parse(readFileSync(localFixturePath, "utf8"));
752
+ link = JSON.parse(readFileSync(linkPath, "utf8"));
777
753
  }
778
- catch (error) {
779
- return this.result("dev", workspace.context.workspace, undefined, [
780
- this.diagnostic("LOCAL_DEVELOPMENT_FIXTURE_INVALID", `本地开发夹具不是有效 JSON: ${error.message}`, localFixturePath, "修复 openxiangda.local.json 后重试"),
781
- ]);
754
+ catch {
755
+ throw new Error("OPENXIANGDA_CONNECTED_LINK_INVALID");
782
756
  }
783
- }
784
- const local = await runLocalDevelopment({
785
- root: workspace.root,
786
- appCode: workspace.config.app.code,
787
- runtimeOAuthScopes: [
788
- ...new Set((workspace.config.authz?.roles || []).flatMap(role => role.capabilities || [])),
789
- ],
790
- eventSubscriptionCodes: (workspace.config.events?.subscriptions || []).map(subscription => subscription.code),
791
- localManifest: {
792
- schemaVersion: 1,
757
+ if (link.schemaVersion !== 2 || link.appCode !== workspace.config.app.code || !link.baseUrl) {
758
+ throw new Error("OPENXIANGDA_CONNECTED_LINK_INVALID");
759
+ }
760
+ const developerSession = await OpenXiangdaDeveloperSession.load();
761
+ if (!developerSession) {
762
+ throw new Error("OPENXIANGDA_CONNECTED_LOGIN_REQUIRED: 先运行 openxiangda login");
763
+ }
764
+ developerSession.assertPlatform(link.baseUrl);
765
+ await developerSession.whoami();
766
+ const client = new OpenXiangdaControlPlaneClient({
767
+ baseUrl: developerSession.baseUrl,
768
+ tokenProvider: developerSession,
769
+ });
770
+ const environments = await client.applicationEnvironments(workspace.config.app.code);
771
+ const environment = selectConnectedDevelopmentEnvironment(environments.items);
772
+ if (!environment) {
773
+ throw new Error("OPENXIANGDA_CONNECTED_ACTIVE_ENVIRONMENT_REQUIRED: 应用需要已发布的 test(preproduction) 或 production 环境");
774
+ }
775
+ const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
776
+ const remoteSession = {
777
+ create: async () => {
778
+ const value = await client.createConnectedDevelopmentSession(workspace.config.app.code, {
779
+ environmentKey: environment.environmentKey,
780
+ manifestDigest: sources.config.digest,
781
+ });
782
+ return {
783
+ id: value.sessionId,
784
+ token: value.sessionToken,
785
+ expiresAt: value.expiresAt,
786
+ mode: value.mode,
787
+ manifestOverlay: value.manifestOverlay,
788
+ manifestDigest: value.manifestDigest,
789
+ };
790
+ },
791
+ current: async (token) => {
792
+ const value = await client.connectedDevelopmentSession(workspace.config.app.code, token);
793
+ return {
794
+ id: value.sessionId,
795
+ expiresAt: value.expiresAt,
796
+ mode: value.mode,
797
+ manifestOverlay: value.manifestOverlay,
798
+ manifestDigest: value.manifestDigest,
799
+ };
800
+ },
801
+ refresh: async (token) => {
802
+ const value = await client.refreshConnectedDevelopmentSession(workspace.config.app.code, token);
803
+ return {
804
+ id: value.sessionId,
805
+ expiresAt: value.expiresAt,
806
+ mode: value.mode,
807
+ manifestOverlay: value.manifestOverlay,
808
+ manifestDigest: value.manifestDigest,
809
+ };
810
+ },
811
+ revoke: async (token) => {
812
+ await client.revokeConnectedDevelopmentSession(workspace.config.app.code, token);
813
+ },
814
+ };
815
+ input.onStatus?.("首版 connected dev 使用已发布资源;检测到未发布字段时请先部署 test");
816
+ const connected = await runConnectedDevelopment({
817
+ root: workspace.root,
793
818
  appCode: workspace.config.app.code,
794
- resources: workspace.config.data?.resources || [],
795
- roles: workspace.config.authz?.roles || [],
796
- scopeDimensions: workspace.config.authz?.scopeDimensions || [],
797
- dataPolicies: workspace.config.authz?.dataPolicies || [],
798
- backendSecrets: workspace.config.backend?.secrets || [],
799
- eventSubscriptions: workspace.config.events?.subscriptions || [],
800
- timerSubscriptions: workspace.config.events?.timers || [],
801
- workflowDefinitions: workspace.config.workflows?.definitions || [],
802
- workflowBindings: workspace.config.workflows?.bindings || [],
803
- workflowActivations: workspace.config.workflows?.activations || [],
804
- workflowProviders: workspace.config.workflows?.providers || [],
805
- ...(localFixture ? { fixture: localFixture } : {}),
806
- },
807
- ...(input.noOpen === undefined ? {} : { noOpen: input.noOpen }),
808
- ...(input.reset === undefined ? {} : { reset: input.reset }),
809
- ...(input.uiOnly === undefined ? {} : { uiOnly: input.uiOnly }),
810
- ...(input.webPort === undefined ? {} : { webPort: input.webPort }),
811
- ...(input.onStatus ? { onStatus: input.onStatus } : {}),
812
- });
813
- const diagnostics = local.exitCode === 0
814
- ? []
815
- : [
816
- this.diagnostic("DEV_PROCESS_FAILED", "本地开发进程异常退出", local.logPath, "查看本地诊断和日志后重试", { diagnosticsPath: local.diagnosticsPath }),
817
- ];
818
- return this.result("dev", workspace.context.workspace, local, diagnostics);
819
+ platformBaseUrl: developerSession.baseUrl,
820
+ environment,
821
+ developerSession,
822
+ remoteSession,
823
+ ...(input.noOpen === undefined ? {} : { noOpen: input.noOpen }),
824
+ ...(input.webPort === undefined ? {} : { webPort: input.webPort }),
825
+ ...(input.onStatus ? { onStatus: input.onStatus } : {}),
826
+ });
827
+ const diagnostics = connected.exitCode === 0
828
+ ? []
829
+ : [
830
+ this.diagnostic("DEV_PROCESS_FAILED", "连接式开发进程异常退出", workspace.root, "查看 Web Nest 进程的终端输出后重试"),
831
+ ];
832
+ return this.result("dev", workspace.context.workspace, connected, diagnostics);
833
+ }
834
+ catch (error) {
835
+ const failure = this.connectedDevelopmentFailure(error, workspace.root);
836
+ return this.result("dev", workspace.context.workspace, undefined, [
837
+ this.diagnostic(failure.code, failure.message, failure.path, failure.remediation),
838
+ ], [{ code: failure.actionCode, label: failure.actionLabel, command: failure.command }]);
839
+ }
819
840
  }
820
841
  async workspace(root) {
821
842
  return await loadWorkspace(root, this.toolchainVersion);
@@ -996,6 +1017,82 @@ export class OpenXiangdaApplicationServices {
996
1017
  ...(details ? { details } : {}),
997
1018
  };
998
1019
  }
1020
+ connectedDevelopmentFailure(error, workspaceRoot) {
1021
+ const rawCode = String(error?.code ||
1022
+ (error instanceof Error ? error.message.split(":", 1)[0] : "") ||
1023
+ "OPENXIANGDA_CONNECTED_DEV_FAILED").trim();
1024
+ const known = {
1025
+ OPENXIANGDA_CONNECTED_LINK_REQUIRED: {
1026
+ message: "当前工作区尚未绑定 OpenXiangda 平台",
1027
+ remediation: "运行 create 幂等完成工作区绑定和平台初始化",
1028
+ actionCode: "create",
1029
+ actionLabel: "初始化应用",
1030
+ command: "openxiangda create <directory>",
1031
+ path: ".openxiangda/link.json",
1032
+ },
1033
+ OPENXIANGDA_CONNECTED_LINK_INVALID: {
1034
+ message: "当前工作区的平台绑定无效或属于另一个应用",
1035
+ remediation: "运行 create 幂等校验并重新生成当前应用绑定",
1036
+ actionCode: "create",
1037
+ actionLabel: "重新初始化应用",
1038
+ command: "openxiangda create <directory>",
1039
+ path: ".openxiangda/link.json",
1040
+ },
1041
+ OPENXIANGDA_CONNECTED_LOGIN_REQUIRED: {
1042
+ message: "尚未登录 OpenXiangda 平台",
1043
+ remediation: "登录工作区绑定的平台后重试",
1044
+ actionCode: "login",
1045
+ actionLabel: "登录平台",
1046
+ command: "openxiangda login --base-url <platform>",
1047
+ },
1048
+ OPENXIANGDA_AUTH_REQUIRED: {
1049
+ message: "OpenXiangda 登录已失效",
1050
+ remediation: "重新登录工作区绑定的平台后重试",
1051
+ actionCode: "login",
1052
+ actionLabel: "重新登录",
1053
+ command: "openxiangda login --base-url <platform>",
1054
+ },
1055
+ OPENXIANGDA_PLATFORM_SESSION_MISMATCH: {
1056
+ message: "工作区绑定平台与当前登录平台不一致",
1057
+ remediation: "切换登录平台,或明确重新绑定当前工作区",
1058
+ actionCode: "login",
1059
+ actionLabel: "切换登录平台",
1060
+ command: "openxiangda login --base-url <linked-platform>",
1061
+ path: ".openxiangda/link.json",
1062
+ },
1063
+ OPENXIANGDA_CONNECTED_ACTIVE_ENVIRONMENT_REQUIRED: {
1064
+ message: "应用没有可用于 connected dev 的已发布环境",
1065
+ remediation: "先发布 test(preproduction);只有 production 时也可以直接连接正式数据",
1066
+ actionCode: "deploy.test",
1067
+ actionLabel: "部署 test",
1068
+ command: "openxiangda deploy",
1069
+ },
1070
+ };
1071
+ const dataRemediation = error
1072
+ ?.data?.remediation;
1073
+ const remoteRemediation = error instanceof ControlPlaneError
1074
+ ? error.remote?.remediation ||
1075
+ (typeof dataRemediation === "string" ? dataRemediation : undefined)
1076
+ : undefined;
1077
+ const selected = known[rawCode] || {
1078
+ message: "连接式开发预检失败",
1079
+ remediation: remoteRemediation || (error instanceof Error && error.message
1080
+ ? error.message.replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
1081
+ : "检查平台登录、应用权限和 Dev Session 状态后重试"),
1082
+ actionCode: "dev.retry",
1083
+ actionLabel: "重试 connected dev",
1084
+ command: "openxiangda dev",
1085
+ };
1086
+ return {
1087
+ code: rawCode,
1088
+ message: selected.message,
1089
+ remediation: selected.remediation,
1090
+ actionCode: selected.actionCode,
1091
+ actionLabel: selected.actionLabel,
1092
+ command: selected.command,
1093
+ path: selected.path || workspaceRoot,
1094
+ };
1095
+ }
999
1096
  ok(operation, workspace, data, nextActions = []) {
1000
1097
  return {
1001
1098
  ok: true,