openxiangda-devkit-core 2.0.0-alpha.12

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 (46) hide show
  1. package/README.md +12 -0
  2. package/dist/application-services.d.ts +229 -0
  3. package/dist/application-services.d.ts.map +1 -0
  4. package/dist/application-services.js +660 -0
  5. package/dist/application-services.js.map +1 -0
  6. package/dist/command-registry.d.ts +460 -0
  7. package/dist/command-registry.d.ts.map +1 -0
  8. package/dist/command-registry.js +271 -0
  9. package/dist/command-registry.js.map +1 -0
  10. package/dist/config.d.ts +2 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +2 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/control-plane-client.d.ts +446 -0
  15. package/dist/control-plane-client.d.ts.map +1 -0
  16. package/dist/control-plane-client.js +651 -0
  17. package/dist/control-plane-client.js.map +1 -0
  18. package/dist/deployment.d.ts +28 -0
  19. package/dist/deployment.d.ts.map +1 -0
  20. package/dist/deployment.js +177 -0
  21. package/dist/deployment.js.map +1 -0
  22. package/dist/index.d.ts +11 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +11 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/package-compiler.d.ts +2 -0
  27. package/dist/package-compiler.d.ts.map +1 -0
  28. package/dist/package-compiler.js +2 -0
  29. package/dist/package-compiler.js.map +1 -0
  30. package/dist/session.d.ts +20 -0
  31. package/dist/session.d.ts.map +1 -0
  32. package/dist/session.js +48 -0
  33. package/dist/session.js.map +1 -0
  34. package/dist/version.d.ts +3 -0
  35. package/dist/version.d.ts.map +1 -0
  36. package/dist/version.js +11 -0
  37. package/dist/version.js.map +1 -0
  38. package/dist/workspace-loader.d.ts +17 -0
  39. package/dist/workspace-loader.d.ts.map +1 -0
  40. package/dist/workspace-loader.js +123 -0
  41. package/dist/workspace-loader.js.map +1 -0
  42. package/dist/workspace.d.ts +15 -0
  43. package/dist/workspace.d.ts.map +1 -0
  44. package/dist/workspace.js +34 -0
  45. package/dist/workspace.js.map +1 -0
  46. package/package.json +37 -0
@@ -0,0 +1,660 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from "node:fs";
3
+ import { dirname, join, relative } from "node:path";
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";
7
+ import { loadSession } from "./session.js";
8
+ import { OPENXIANGDA_TOOLCHAIN_VERSION } from "./version.js";
9
+ import { git, loadWorkspace, } from "./workspace-loader.js";
10
+ export class OpenXiangdaApplicationServices {
11
+ options;
12
+ constructor(options = {}) {
13
+ this.options = options;
14
+ }
15
+ async workspaceContext(root) {
16
+ const workspace = await this.workspace(root);
17
+ return this.ok("workspace.context", workspace.context.workspace, workspace.context);
18
+ }
19
+ async contractDescribe(root) {
20
+ const workspace = await this.workspace(root);
21
+ const sources = compileApplicationSources(workspace.config);
22
+ return this.ok("contract.describe", workspace.context.workspace, {
23
+ configDigest: sources.config.digest,
24
+ contractDigest: sources.contracts.digest,
25
+ contract: sources.contracts.value,
26
+ counts: {
27
+ backendSecrets: sources.config.value.backendSecrets.length,
28
+ dataResources: sources.config.value.dataResources.length,
29
+ eventSubscriptions: sources.config.value.eventSubscriptions.length,
30
+ timerSubscriptions: sources.config.value.timerSubscriptions?.length || 0,
31
+ workflowDefinitions: sources.config.value.workflowDefinitions.length,
32
+ workflowBindings: sources.config.value.workflowBindings.length,
33
+ workflowProviders: sources.config.value.workflowProviders?.length || 0,
34
+ },
35
+ });
36
+ }
37
+ async platformCapabilities(root) {
38
+ const workspace = await this.workspace(root);
39
+ const capabilities = await (await this.client(workspace.root)).capabilities();
40
+ return this.ok("platform.capabilities", workspace.context.workspace, capabilities);
41
+ }
42
+ async provisionApplication(root) {
43
+ const workspace = await this.workspace(root);
44
+ const application = await (await this.client(workspace.root)).provisionApplication({
45
+ appCode: workspace.config.app.code,
46
+ name: workspace.config.app.name,
47
+ });
48
+ return this.ok("app.provision", workspace.context.workspace, application, [
49
+ { code: "check", label: "检查应用", command: "openxiangda check" },
50
+ ]);
51
+ }
52
+ async oauthClients(root) {
53
+ const workspace = await this.workspace(root);
54
+ const data = await (await this.client(workspace.root)).oauthClients(workspace.config.app.code);
55
+ return this.ok("oauth.client.list", workspace.context.workspace, data);
56
+ }
57
+ async runtimeOAuthCredentialStatus(root, environmentKey) {
58
+ const workspace = await this.workspace(root);
59
+ const data = await (await this.client(workspace.root)).runtimeOAuthCredentialStatus(workspace.config.app.code, environmentKey);
60
+ return this.ok("oauth.runtime.status", workspace.context.workspace, data);
61
+ }
62
+ async rotateRuntimeOAuthCredential(root, input) {
63
+ const workspace = await this.workspace(root);
64
+ const client = await this.client(workspace.root);
65
+ const status = await client.runtimeOAuthCredentialStatus(workspace.config.app.code, input.environmentKey);
66
+ if (!status.configured || !status.client) {
67
+ throw new Error("OAUTH2_RUNTIME_CLIENT_NOT_PROVISIONED");
68
+ }
69
+ const rotation = await client.stageRuntimeOAuthCredentialRotation(workspace.config.app.code, input.environmentKey, {
70
+ expectedCredentialVersion: status.client.credentialVersion,
71
+ ...(input.gracePeriodSeconds === undefined
72
+ ? {}
73
+ : { gracePeriodSeconds: input.gracePeriodSeconds }),
74
+ idempotencyKey: input.idempotencyKey,
75
+ });
76
+ const head = await client.environmentHead(workspace.config.app.code, input.environmentKey);
77
+ if (!head.activeAppVersionId) {
78
+ throw new Error("OAUTH2_RUNTIME_ROTATION_ACTIVE_VERSION_REQUIRED");
79
+ }
80
+ const deployment = await client.promote({
81
+ appCode: workspace.config.app.code,
82
+ appVersionId: head.activeAppVersionId,
83
+ ...(head.environmentId
84
+ ? { environmentId: head.environmentId }
85
+ : { environmentKind: head.environmentKind }),
86
+ idempotencyKey: `oauth-runtime-rotation:${input.idempotencyKey}`,
87
+ requestId: input.idempotencyKey,
88
+ });
89
+ return this.ok("oauth.runtime.rotate", workspace.context.workspace, {
90
+ rotation,
91
+ deployment,
92
+ next: {
93
+ command: `openxiangda status ${deployment.id}`,
94
+ credentialActivatesDuringDeployment: true,
95
+ },
96
+ });
97
+ }
98
+ async createOAuthClient(root, input) {
99
+ const workspace = await this.workspace(root);
100
+ const data = await (await this.client(workspace.root)).createOAuthClient(workspace.config.app.code, input);
101
+ return this.ok("oauth.client.create", workspace.context.workspace, data);
102
+ }
103
+ async updateOAuthClient(root, clientId, input) {
104
+ const workspace = await this.workspace(root);
105
+ const data = await (await this.client(workspace.root)).updateOAuthClient(workspace.config.app.code, clientId, input);
106
+ return this.ok("oauth.client.update", workspace.context.workspace, data);
107
+ }
108
+ async rotateOAuthClientSecret(root, clientId, gracePeriodSeconds = 600) {
109
+ const workspace = await this.workspace(root);
110
+ const data = await (await this.client(workspace.root)).rotateOAuthClientSecret(workspace.config.app.code, clientId, gracePeriodSeconds);
111
+ return this.ok("oauth.client.rotate", workspace.context.workspace, data);
112
+ }
113
+ async revokeOAuthClient(root, clientId) {
114
+ const workspace = await this.workspace(root);
115
+ const data = await (await this.client(workspace.root)).revokeOAuthClient(workspace.config.app.code, clientId);
116
+ return this.ok("oauth.client.revoke", workspace.context.workspace, data);
117
+ }
118
+ async oauthAuditEvents(root, input = {}) {
119
+ const workspace = await this.workspace(root);
120
+ const data = await (await this.client(workspace.root)).oauthAuditEvents(workspace.config.app.code, input);
121
+ return this.ok("oauth.audit.list", workspace.context.workspace, data);
122
+ }
123
+ async applicationSecrets(root, environmentKey) {
124
+ const workspace = await this.workspace(root);
125
+ const data = await (await this.client(workspace.root)).applicationSecrets(workspace.config.app.code, environmentKey);
126
+ return this.ok("secret.list", workspace.context.workspace, data);
127
+ }
128
+ async createApplicationSecret(root, environmentKey, input) {
129
+ const workspace = await this.workspace(root);
130
+ const data = await (await this.client(workspace.root)).createApplicationSecret(workspace.config.app.code, environmentKey, input);
131
+ return this.ok("secret.create", workspace.context.workspace, data);
132
+ }
133
+ async updateApplicationSecret(root, environmentKey, name, input) {
134
+ const workspace = await this.workspace(root);
135
+ const data = await (await this.client(workspace.root)).updateApplicationSecret(workspace.config.app.code, environmentKey, name, input);
136
+ return this.ok("secret.update", workspace.context.workspace, data);
137
+ }
138
+ async rotateApplicationSecret(root, environmentKey, name, input) {
139
+ const workspace = await this.workspace(root);
140
+ const data = await (await this.client(workspace.root)).rotateApplicationSecret(workspace.config.app.code, environmentKey, name, input);
141
+ return this.ok("secret.rotate", workspace.context.workspace, data);
142
+ }
143
+ async deleteApplicationSecret(root, environmentKey, name, input) {
144
+ const workspace = await this.workspace(root);
145
+ const data = await (await this.client(workspace.root)).deleteApplicationSecret(workspace.config.app.code, environmentKey, name, input);
146
+ return this.ok("secret.delete", workspace.context.workspace, data);
147
+ }
148
+ async applicationSecretAuditEvents(root, environmentKey, name, limit = 100) {
149
+ const workspace = await this.workspace(root);
150
+ const data = await (await this.client(workspace.root)).applicationSecretAuditEvents(workspace.config.app.code, environmentKey, name, limit);
151
+ return this.ok("secret.audit.list", workspace.context.workspace, data);
152
+ }
153
+ async eventSubscriptions(root) {
154
+ const workspace = await this.workspace(root);
155
+ const data = await (await this.client(workspace.root)).eventSubscriptions(workspace.config.app.code);
156
+ return this.ok("event.subscription.list", workspace.context.workspace, data);
157
+ }
158
+ async setEventSubscriptionStatus(root, subscriptionId, input) {
159
+ const workspace = await this.workspace(root);
160
+ const data = await (await this.client(workspace.root)).setEventSubscriptionStatus(workspace.config.app.code, subscriptionId, input);
161
+ return this.ok("event.subscription.status", workspace.context.workspace, data);
162
+ }
163
+ async rotateEventSubscriptionSecret(root, subscriptionId, input) {
164
+ const workspace = await this.workspace(root);
165
+ const data = await (await this.client(workspace.root)).rotateEventSubscriptionSecret(workspace.config.app.code, subscriptionId, input);
166
+ return this.ok("event.subscription.rotate", workspace.context.workspace, data);
167
+ }
168
+ async eventDeliveries(root, limit = 100) {
169
+ const workspace = await this.workspace(root);
170
+ const data = await (await this.client(workspace.root)).eventDeliveries(workspace.config.app.code, limit);
171
+ return this.ok("event.delivery.list", workspace.context.workspace, data);
172
+ }
173
+ async replayEventDelivery(root, deliveryId, idempotencyKey) {
174
+ const workspace = await this.workspace(root);
175
+ const data = await (await this.client(workspace.root)).replayEventDelivery(workspace.config.app.code, deliveryId, idempotencyKey);
176
+ return this.ok("event.delivery.replay", workspace.context.workspace, data);
177
+ }
178
+ async timerSubscriptions(root) {
179
+ const workspace = await this.workspace(root);
180
+ const data = await (await this.client(workspace.root)).timerSubscriptions(workspace.config.app.code);
181
+ return this.ok("event.timer.list", workspace.context.workspace, data);
182
+ }
183
+ async setTimerSubscriptionStatus(root, timerId, input) {
184
+ const workspace = await this.workspace(root);
185
+ const data = await (await this.client(workspace.root)).setTimerSubscriptionStatus(workspace.config.app.code, timerId, input);
186
+ return this.ok("event.timer.status", workspace.context.workspace, data);
187
+ }
188
+ async workflowAssigneeProviders(root, environmentKey) {
189
+ const workspace = await this.workspace(root);
190
+ const data = await (await this.client(workspace.root)).workflowAssigneeProviders(workspace.config.app.code, environmentKey);
191
+ return this.ok("workflow.provider.list", workspace.context.workspace, data);
192
+ }
193
+ async rotateWorkflowAssigneeProviderSecret(root, providerCode, input) {
194
+ const workspace = await this.workspace(root);
195
+ const data = await (await this.client(workspace.root)).rotateWorkflowAssigneeProviderSecret(workspace.config.app.code, providerCode, input);
196
+ return this.ok("workflow.provider.rotate", workspace.context.workspace, data);
197
+ }
198
+ async linkApplication(root, input) {
199
+ const workspace = await this.workspace(root);
200
+ const path = join(workspace.root, ".openxiangda", "link.json");
201
+ mkdirSync(dirname(path), { recursive: true });
202
+ const value = {
203
+ schemaVersion: 2,
204
+ appCode: workspace.config.app.code,
205
+ baseUrl: input.baseUrl.replace(/\/+$/, ""),
206
+ environments: input.environments || [],
207
+ };
208
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
209
+ return this.ok("app.link", workspace.context.workspace, { path, ...value });
210
+ }
211
+ async generate(input = {}) {
212
+ const workspace = await this.workspace(input.root);
213
+ const sources = compileApplicationSources(workspace.config);
214
+ const output = join(workspace.root, "packages/contracts/src/generated.ts");
215
+ const current = existsSync(output) ? readFileSync(output, "utf8") : "";
216
+ const changed = current !== sources.contracts.typescript;
217
+ const diagnostics = [];
218
+ if (input.check && changed) {
219
+ diagnostics.push(this.diagnostic("GENERATED_CONTRACTS_OUTDATED", "生成契约与应用声明不一致", relative(workspace.root, output), "运行 openxiangda generate 并提交生成结果"));
220
+ }
221
+ else if (changed) {
222
+ mkdirSync(dirname(output), { recursive: true });
223
+ writeFileSync(output, sources.contracts.typescript, "utf8");
224
+ }
225
+ return this.result("generate", workspace.context.workspace, {
226
+ output,
227
+ changed,
228
+ contractDigest: sources.contracts.digest,
229
+ }, diagnostics, changed && input.check
230
+ ? [
231
+ {
232
+ code: "generate",
233
+ label: "更新生成契约",
234
+ command: "openxiangda generate",
235
+ },
236
+ ]
237
+ : []);
238
+ }
239
+ async check(root) {
240
+ const workspace = await this.workspace(root);
241
+ const diagnostics = [
242
+ ...validateAppConfig(workspace.config),
243
+ ...this.versionTrainDiagnostics(workspace),
244
+ ];
245
+ const generated = await this.generate({
246
+ root: workspace.root,
247
+ check: true,
248
+ });
249
+ diagnostics.push(...generated.diagnostics);
250
+ const command = this.runPackageScript(workspace.root, "check");
251
+ if (!command.ok) {
252
+ diagnostics.push(this.diagnostic("WORKSPACE_CHECK_FAILED", "工作区 check 脚本失败", "package.json#scripts.check", "修复类型、契约或静态检查错误后重试", { output: command.output }));
253
+ }
254
+ return this.result("check", workspace.context.workspace, { stages: [{ name: "workspace", ...command }] }, diagnostics);
255
+ }
256
+ async test(root) {
257
+ const workspace = await this.workspace(root);
258
+ const command = this.runPackageScript(workspace.root, "test");
259
+ const diagnostics = command.ok
260
+ ? []
261
+ : [
262
+ this.diagnostic("WORKSPACE_TEST_FAILED", "工作区测试失败", "package.json#scripts.test", "修复失败测试后重试", { output: command.output }),
263
+ ];
264
+ return this.result("test", workspace.context.workspace, command, diagnostics);
265
+ }
266
+ async build(input = {}) {
267
+ const sealed = await this.buildSealed(input);
268
+ if (!sealed.data)
269
+ return sealed;
270
+ return {
271
+ ...sealed,
272
+ data: {
273
+ package: sealed.data.package,
274
+ ...(sealed.data.outputDirectory
275
+ ? { outputDirectory: sealed.data.outputDirectory }
276
+ : {}),
277
+ },
278
+ };
279
+ }
280
+ async buildSealed(input = {}) {
281
+ const workspace = await this.workspace(input.root);
282
+ const diagnostics = [];
283
+ await this.generate({ root: workspace.root });
284
+ if (!input.skipWorkspaceBuild) {
285
+ const command = this.runPackageScript(workspace.root, "build");
286
+ if (!command.ok) {
287
+ diagnostics.push(this.diagnostic("WORKSPACE_BUILD_FAILED", "前后端生产构建失败", "package.json#scripts.build", "修复构建错误后重试", { output: command.output }));
288
+ return this.result("build", workspace.context.workspace, undefined, diagnostics);
289
+ }
290
+ }
291
+ const backendImage = String(input.backendImage || process.env.OPENXIANGDA_BACKEND_IMAGE || "").trim();
292
+ if (!backendImage) {
293
+ diagnostics.push(this.diagnostic("BACKEND_IMAGE_REQUIRED", "当前应用包含 NestJS 后端,密封 AppPackage 时必须提供不可变 OCI image", "backend.image", "通过 --backend-image <registry/repository@sha256:digest> 或 OPENXIANGDA_BACKEND_IMAGE 提供"));
294
+ return this.result("build", workspace.context.workspace, undefined, diagnostics);
295
+ }
296
+ if (!/@sha256:[a-f0-9]{64}$/i.test(backendImage)) {
297
+ diagnostics.push(this.diagnostic("BACKEND_IMAGE_NOT_IMMUTABLE", "后端镜像必须使用不可变 sha256 digest,不能使用可变 tag", "backend.image", "将镜像引用改为 <registry/repository@sha256:64位摘要>"));
298
+ return this.result("build", workspace.context.workspace, undefined, diagnostics);
299
+ }
300
+ const sources = compileApplicationSources(workspace.config);
301
+ const frontendContent = this.directoryBundle(join(workspace.root, workspace.config.frontend.root, "dist"));
302
+ const frontendDigest = sha256Bytes(frontendContent);
303
+ const backendContent = canonicalJson({ image: backendImage });
304
+ const backendDigest = sha256Bytes(backendContent);
305
+ const artifacts = [
306
+ {
307
+ kind: "frontend",
308
+ digest: frontendDigest,
309
+ mediaType: "application/vnd.openxiangda.frontend-bundle.v2+json",
310
+ size: Buffer.byteLength(frontendContent),
311
+ entrypoint: "index.html",
312
+ },
313
+ {
314
+ kind: "backend",
315
+ digest: backendDigest,
316
+ mediaType: "application/vnd.oci.image.manifest.v1+json",
317
+ size: Buffer.byteLength(backendContent),
318
+ metadata: {
319
+ imageDigest: backendImage,
320
+ port: 3000,
321
+ },
322
+ },
323
+ sources.config.artifact,
324
+ sources.contracts.artifact,
325
+ ];
326
+ const revision = workspace.context.workspace.revision || "0".repeat(40);
327
+ const packageVersion = String(workspace.packageJson.version || "0.0.0");
328
+ const compiled = compileAppPackage({
329
+ config: workspace.config,
330
+ version: `${packageVersion}-${revision.slice(0, 12)}`,
331
+ createdAt: git(workspace.root, ["show", "-s", "--format=%cI", "HEAD"]) ||
332
+ new Date(0).toISOString(),
333
+ source: {
334
+ repository: workspace.context.workspace.repository || `local:${workspace.root}`,
335
+ commit: revision,
336
+ dirty: workspace.context.workspace.dirty === true,
337
+ },
338
+ toolchainVersion: this.toolchainVersion,
339
+ artifacts,
340
+ manifests: {
341
+ frontend: frontendDigest,
342
+ backend: backendDigest,
343
+ config: sources.config.digest,
344
+ dataContract: sources.contracts.digest,
345
+ },
346
+ minimumPlatformVersion: "2.0.0-alpha.1",
347
+ metadata: {
348
+ runtimeProfile: workspace.config.backend.runMode || "shared",
349
+ },
350
+ });
351
+ const artifactContent = {
352
+ [frontendDigest]: frontendContent,
353
+ [backendDigest]: backendContent,
354
+ [sources.config.digest]: sources.config.content,
355
+ [sources.contracts.digest]: sources.contracts.content,
356
+ };
357
+ let outputDirectory;
358
+ if (input.write !== false) {
359
+ outputDirectory = join(workspace.root, ".openxiangda", "build");
360
+ mkdirSync(join(outputDirectory, "artifacts"), { recursive: true });
361
+ writeFileSync(join(outputDirectory, "app-package.json"), `${JSON.stringify(compiled.manifest, null, 2)}\n`, "utf8");
362
+ for (const [digest, content] of Object.entries(artifactContent)) {
363
+ writeFileSync(join(outputDirectory, "artifacts", digest), content, "utf8");
364
+ }
365
+ }
366
+ return this.result("build", workspace.context.workspace, {
367
+ package: compiled,
368
+ artifactContent,
369
+ ...(outputDirectory ? { outputDirectory } : {}),
370
+ }, diagnostics, [
371
+ {
372
+ code: "deploy",
373
+ label: "部署到预发",
374
+ command: "openxiangda deploy preproduction",
375
+ },
376
+ ]);
377
+ }
378
+ async deploymentPlan(input) {
379
+ const built = await this.build({ ...input, write: false });
380
+ if (!built.ok || !built.data)
381
+ return built;
382
+ return {
383
+ ...built,
384
+ operation: "deployment.plan",
385
+ data: {
386
+ environment: input.environment,
387
+ packageDigest: built.data.package.digest,
388
+ appCode: built.data.package.manifest.appCode,
389
+ artifacts: built.data.package.manifest.artifacts,
390
+ requiredCapabilities: built.data.package.manifest.compatibility.requiredCapabilities,
391
+ },
392
+ };
393
+ }
394
+ async deploy(input) {
395
+ const workspace = await this.workspace(input.root);
396
+ if (input.environment === "production" &&
397
+ workspace.context.workspace.dirty) {
398
+ return this.result("deploy", workspace.context.workspace, undefined, [
399
+ this.diagnostic("PRODUCTION_SOURCE_DIRTY", "生产部署只接受 clean Git commit", "git", "提交并推送全部变更后重新构建同一 AppVersion"),
400
+ ]);
401
+ }
402
+ const built = await this.buildSealed({ ...input, root: workspace.root });
403
+ if (!built.ok || !built.data)
404
+ return built;
405
+ const client = await this.client(workspace.root);
406
+ const deployment = await (await import("./deployment.js")).submitAppPackage({
407
+ client,
408
+ compiledPackage: built.data.package,
409
+ artifactContent: built.data.artifactContent,
410
+ environmentKind: input.environment,
411
+ ...(input.environmentId ? { environmentId: input.environmentId } : {}),
412
+ idempotencyKey: input.idempotencyKey ||
413
+ `deploy:${built.data.package.digest}:${input.environment}`,
414
+ ...(input.requestId ? { requestId: input.requestId } : {}),
415
+ });
416
+ return this.ok("deploy", workspace.context.workspace, deployment, [
417
+ {
418
+ code: "status",
419
+ label: "查看部署状态",
420
+ command: `openxiangda status ${deployment.id}`,
421
+ },
422
+ ]);
423
+ }
424
+ async deploymentStatus(root, deploymentId) {
425
+ const workspace = await this.workspace(root);
426
+ const client = await this.client(workspace.root);
427
+ const data = deploymentId
428
+ ? await client.deployment(workspace.config.app.code, deploymentId)
429
+ : await client.deployments(workspace.config.app.code, 20);
430
+ return this.ok("status", workspace.context.workspace, data);
431
+ }
432
+ async deploymentLogs(root, deploymentId) {
433
+ const status = await this.deploymentStatus(root, deploymentId);
434
+ const run = status.data;
435
+ return {
436
+ ...status,
437
+ operation: "logs",
438
+ data: {
439
+ deploymentId,
440
+ status: run.status,
441
+ stage: run.stage,
442
+ checkpoints: run.checkpoints,
443
+ failure: run.failure,
444
+ traceId: run.result?.traceId,
445
+ },
446
+ };
447
+ }
448
+ async retry(root, deploymentId) {
449
+ const workspace = await this.workspace(root);
450
+ const deployment = await (await this.client(workspace.root)).retryDeployment(workspace.config.app.code, deploymentId);
451
+ return this.ok("retry", workspace.context.workspace, deployment);
452
+ }
453
+ async cancel(root, deploymentId) {
454
+ const workspace = await this.workspace(root);
455
+ const deployment = await (await this.client(workspace.root)).cancelDeployment(workspace.config.app.code, deploymentId);
456
+ return this.ok("cancel", workspace.context.workspace, deployment);
457
+ }
458
+ async promote(root, deploymentId, environment) {
459
+ const workspace = await this.workspace(root);
460
+ const client = await this.client(workspace.root);
461
+ const source = await client.deployment(workspace.config.app.code, deploymentId);
462
+ const appVersionId = String(source.result?.applicationVersionId || "");
463
+ if (!appVersionId)
464
+ throw new Error("DEPLOYMENT_APP_VERSION_NOT_AVAILABLE");
465
+ const deployment = await client.promote({
466
+ appCode: workspace.config.app.code,
467
+ appVersionId,
468
+ environmentKind: environment,
469
+ idempotencyKey: `promote:${source.packageDigest}:${environment}`,
470
+ });
471
+ return this.ok("promote", workspace.context.workspace, deployment);
472
+ }
473
+ async rollback(root, environment, appVersionId) {
474
+ const workspace = await this.workspace(root);
475
+ const deployment = await (await this.client(workspace.root)).rollback({
476
+ appCode: workspace.config.app.code,
477
+ appVersionId,
478
+ environmentKind: environment,
479
+ idempotencyKey: `rollback:${appVersionId}:${environment}`,
480
+ });
481
+ return this.ok("rollback", workspace.context.workspace, deployment);
482
+ }
483
+ async doctor(root) {
484
+ const workspace = await this.workspace(root);
485
+ const commands = ["git", "node", "pnpm", "docker"].map((command) => {
486
+ const result = spawnSync(command, ["--version"], { encoding: "utf8" });
487
+ return {
488
+ command,
489
+ ok: result.status === 0,
490
+ version: String(result.stdout || result.stderr || "").trim(),
491
+ };
492
+ });
493
+ let platform;
494
+ try {
495
+ platform = (await (await this.client(workspace.root)).capabilities());
496
+ }
497
+ catch (error) {
498
+ platform = { ok: false, message: error.message };
499
+ }
500
+ const diagnostics = commands
501
+ .filter((item) => !item.ok && item.command !== "docker")
502
+ .map((item) => this.diagnostic("TOOLCHAIN_COMMAND_MISSING", `缺少必需命令: ${item.command}`, item.command));
503
+ return this.result("doctor", workspace.context.workspace, { commands, platform }, diagnostics);
504
+ }
505
+ async dev(root) {
506
+ const workspace = await this.workspace(root);
507
+ return await new Promise((resolve) => {
508
+ const child = spawn("pnpm", ["run", "dev"], {
509
+ cwd: workspace.root,
510
+ stdio: "inherit",
511
+ });
512
+ child.on("exit", (code) => resolve(this.result("dev", workspace.context.workspace, { exitCode: code ?? 1 }, code === 0
513
+ ? []
514
+ : [
515
+ this.diagnostic("DEV_PROCESS_FAILED", "开发进程异常退出", "dev"),
516
+ ])));
517
+ });
518
+ }
519
+ async workspace(root) {
520
+ return await loadWorkspace(root, this.toolchainVersion);
521
+ }
522
+ async client(root) {
523
+ if (this.options.client)
524
+ return this.options.client;
525
+ if (this.options.clientOptions) {
526
+ return new OpenXiangdaControlPlaneClient(this.options.clientOptions);
527
+ }
528
+ const session = await loadSession();
529
+ if (!session)
530
+ throw new Error("OPENXIANGDA_AUTH_REQUIRED");
531
+ let baseUrl = session.baseUrl;
532
+ if (root && session.savedAt !== "environment") {
533
+ const linkPath = join(root, ".openxiangda", "link.json");
534
+ if (existsSync(linkPath)) {
535
+ const link = JSON.parse(readFileSync(linkPath, "utf8"));
536
+ if (link.baseUrl)
537
+ baseUrl = String(link.baseUrl).replace(/\/+$/, "");
538
+ }
539
+ }
540
+ return new OpenXiangdaControlPlaneClient({
541
+ baseUrl,
542
+ token: session.token,
543
+ });
544
+ }
545
+ get toolchainVersion() {
546
+ return this.options.toolchainVersion || OPENXIANGDA_TOOLCHAIN_VERSION;
547
+ }
548
+ runPackageScript(root, script) {
549
+ const result = spawnSync("pnpm", ["run", script], {
550
+ cwd: root,
551
+ encoding: "utf8",
552
+ env: process.env,
553
+ });
554
+ return {
555
+ ok: result.status === 0,
556
+ exitCode: result.status ?? 1,
557
+ output: `${result.stdout || ""}${result.stderr || ""}`.trim(),
558
+ };
559
+ }
560
+ directoryBundle(root) {
561
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
562
+ throw Object.assign(new Error(`前端构建目录不存在: ${root}`), {
563
+ code: "FRONTEND_DIST_NOT_FOUND",
564
+ });
565
+ }
566
+ const files = [];
567
+ const visit = (directory) => {
568
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
569
+ const path = join(directory, entry.name);
570
+ if (entry.isDirectory())
571
+ visit(path);
572
+ else if (entry.isFile()) {
573
+ const content = readFileSync(path);
574
+ files.push({
575
+ path: relative(root, path).replaceAll("\\", "/"),
576
+ sha256: sha256Bytes(content),
577
+ content: content.toString("base64"),
578
+ });
579
+ }
580
+ }
581
+ };
582
+ visit(root);
583
+ return canonicalJson({ schemaVersion: 2, files });
584
+ }
585
+ versionTrainDiagnostics(workspace) {
586
+ const versions = new Map();
587
+ const packageFiles = [];
588
+ const visit = (directory) => {
589
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
590
+ if (["node_modules", "dist", "coverage", ".git", ".openxiangda"].includes(entry.name)) {
591
+ continue;
592
+ }
593
+ const path = join(directory, entry.name);
594
+ if (entry.isDirectory())
595
+ visit(path);
596
+ else if (entry.isFile() && entry.name === "package.json")
597
+ packageFiles.push(path);
598
+ }
599
+ };
600
+ visit(workspace.root);
601
+ for (const packageFile of packageFiles) {
602
+ const packageJson = JSON.parse(readFileSync(packageFile, "utf8"));
603
+ for (const section of [
604
+ "dependencies",
605
+ "devDependencies",
606
+ "peerDependencies",
607
+ ]) {
608
+ const dependencies = packageJson[section];
609
+ if (!dependencies || typeof dependencies !== "object")
610
+ continue;
611
+ for (const [name, version] of Object.entries(dependencies)) {
612
+ if (!name.startsWith("openxiangda-"))
613
+ continue;
614
+ const normalized = String(version).replace(/^workspace:/, "");
615
+ const locations = versions.get(normalized) || [];
616
+ locations.push(relative(workspace.root, packageFile).replaceAll("\\", "/"));
617
+ versions.set(normalized, locations);
618
+ }
619
+ }
620
+ }
621
+ if (versions.size <= 1)
622
+ return [];
623
+ return [
624
+ this.diagnostic("TOOLCHAIN_VERSION_TRAIN_MISMATCH", `openxiangda-* 依赖不属于同一固定版本列车: ${[...versions.keys()].join(", ")}`, "package.json", "将全部工作区包的 openxiangda-* 依赖升级到同一版本", { versions: Object.fromEntries(versions) }),
625
+ ];
626
+ }
627
+ diagnostic(code, message, path, remediation, details) {
628
+ return {
629
+ schemaVersion: SCHEMA_VERSIONS.diagnostic,
630
+ code,
631
+ severity: "error",
632
+ message,
633
+ path,
634
+ retryable: false,
635
+ ...(remediation ? { remediation } : {}),
636
+ ...(details ? { details } : {}),
637
+ };
638
+ }
639
+ ok(operation, workspace, data, nextActions = []) {
640
+ return {
641
+ ok: true,
642
+ operation,
643
+ workspace,
644
+ data,
645
+ diagnostics: [],
646
+ nextActions,
647
+ };
648
+ }
649
+ result(operation, workspace, data, diagnostics, nextActions = []) {
650
+ return {
651
+ ok: diagnostics.every((item) => item.severity !== "error"),
652
+ operation,
653
+ workspace,
654
+ ...(data === undefined ? {} : { data }),
655
+ diagnostics,
656
+ nextActions,
657
+ };
658
+ }
659
+ }
660
+ //# sourceMappingURL=application-services.js.map