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.
- package/README.md +2 -1
- package/dist/application-services.d.ts +79 -43
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +257 -160
- package/dist/application-services.js.map +1 -1
- package/dist/backend-image-build.d.ts +23 -0
- package/dist/backend-image-build.d.ts.map +1 -0
- package/dist/backend-image-build.js +192 -0
- package/dist/backend-image-build.js.map +1 -0
- package/dist/command-registry.d.ts +26 -576
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +9 -374
- package/dist/command-registry.js.map +1 -1
- package/dist/compiler/bundle.d.ts +23 -0
- package/dist/compiler/bundle.d.ts.map +1 -0
- package/dist/compiler/bundle.js +604 -0
- package/dist/compiler/bundle.js.map +1 -0
- package/dist/compiler/config.d.ts +377 -0
- package/dist/compiler/config.d.ts.map +1 -0
- package/dist/compiler/config.js +1137 -0
- package/dist/compiler/config.js.map +1 -0
- package/dist/compiler/index.d.ts +4 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +4 -0
- package/dist/compiler/index.js.map +1 -0
- package/dist/compiler/package-compiler.d.ts +22 -0
- package/dist/compiler/package-compiler.d.ts.map +1 -0
- package/dist/compiler/package-compiler.js +60 -0
- package/dist/compiler/package-compiler.js.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connected-development.d.ts +62 -0
- package/dist/connected-development.d.ts.map +1 -0
- package/dist/connected-development.js +389 -0
- package/dist/connected-development.js.map +1 -0
- package/dist/control-plane-client.d.ts +50 -1
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +43 -2
- package/dist/control-plane-client.js.map +1 -1
- package/dist/deployment.d.ts +3 -1
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +18 -8
- package/dist/deployment.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/workflow.d.ts +81 -0
- package/dist/internal/workflow.d.ts.map +1 -0
- package/dist/internal/workflow.js +351 -0
- package/dist/internal/workflow.js.map +1 -0
- package/dist/package-compiler.d.ts +1 -1
- package/dist/package-compiler.d.ts.map +1 -1
- package/dist/package-compiler.js +1 -1
- package/dist/package-compiler.js.map +1 -1
- package/dist/testing.d.ts +38 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +46 -0
- package/dist/testing.js.map +1 -0
- package/dist/workspace-loader.d.ts +1 -1
- package/package.json +8 -4
- package/dist/local-development.d.ts +0 -85
- package/dist/local-development.d.ts.map +0 -1
- package/dist/local-development.js +0 -919
- package/dist/local-development.js.map +0 -1
- package/dist/local-postgres.d.ts +0 -32
- package/dist/local-postgres.d.ts.map +0 -1
- package/dist/local-postgres.js +0 -324
- 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 "
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
382
|
-
|
|
383
|
-
|
|
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, {
|
|
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 ||
|
|
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", "
|
|
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", "
|
|
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
|
|
498
|
+
command: "openxiangda deploy",
|
|
506
499
|
},
|
|
507
500
|
]);
|
|
508
501
|
}
|
|
509
502
|
async deploymentPlan(input) {
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
|
521
|
+
this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy;生产环境必须显式复用成功的测试 DeploymentRun"),
|
|
530
522
|
]);
|
|
531
523
|
}
|
|
532
|
-
const
|
|
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,
|
|
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
|
|
625
|
+
async deployProduction(root, deploymentId) {
|
|
600
626
|
const workspace = await this.workspace(root);
|
|
601
|
-
|
|
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("
|
|
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("
|
|
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
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
752
|
+
link = JSON.parse(readFileSync(linkPath, "utf8"));
|
|
777
753
|
}
|
|
778
|
-
catch
|
|
779
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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,
|