openxiangda-devkit-core 2.0.0-alpha.16 → 2.0.0-alpha.19

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.
@@ -1,10 +1,11 @@
1
- import { spawn, spawnSync } from "node:child_process";
1
+ 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
5
  import { compileApplicationSources, compileAppPackage, sha256Bytes, validateAppConfig, } from "openxiangda-compiler";
6
6
  import { OpenXiangdaControlPlaneClient, } 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
9
  import { OPENXIANGDA_TOOLCHAIN_VERSION } from "./version.js";
9
10
  import { git, loadWorkspace, } from "./workspace-loader.js";
10
11
  export class OpenXiangdaApplicationServices {
@@ -18,19 +19,21 @@ export class OpenXiangdaApplicationServices {
18
19
  }
19
20
  async contractDescribe(root) {
20
21
  const workspace = await this.workspace(root);
21
- const sources = compileApplicationSources(workspace.config);
22
+ const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
22
23
  return this.ok("contract.describe", workspace.context.workspace, {
23
24
  configDigest: sources.config.digest,
24
25
  contractDigest: sources.contracts.digest,
25
26
  contract: sources.contracts.value,
26
27
  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,
28
+ backendSecrets: sources.config.value.backend.secrets.length,
29
+ backendOperations: sources.config.value.backend.operations.length,
30
+ frontendRoutes: sources.config.value.frontend.routes.length,
31
+ dataResources: sources.config.value.data.resources.length,
32
+ eventSubscriptions: sources.config.value.events.subscriptions.length,
33
+ timerSubscriptions: sources.config.value.events.timers.length,
34
+ workflowDefinitions: sources.config.value.workflows.definitions.length,
35
+ workflowBindings: sources.config.value.workflows.bindings.length,
36
+ workflowProviders: sources.config.value.workflows.providers.length,
34
37
  },
35
38
  });
36
39
  }
@@ -56,28 +59,30 @@ export class OpenXiangdaApplicationServices {
56
59
  }
57
60
  async runtimeOAuthCredentialStatus(root, environmentKey) {
58
61
  const workspace = await this.workspace(root);
59
- const data = await (await this.client(workspace.root)).runtimeOAuthCredentialStatus(workspace.config.app.code, environmentKey);
62
+ const environment = this.deploymentEnvironment(environmentKey);
63
+ const data = await (await this.client(workspace.root)).runtimeOAuthCredentialStatus(workspace.config.app.code, environment);
60
64
  return this.ok("oauth.runtime.status", workspace.context.workspace, data);
61
65
  }
62
66
  async rotateRuntimeOAuthCredential(root, input) {
63
67
  const workspace = await this.workspace(root);
64
68
  const client = await this.client(workspace.root);
65
- const status = await client.runtimeOAuthCredentialStatus(workspace.config.app.code, input.environmentKey);
69
+ const environment = this.deploymentEnvironment(input.environmentKey);
70
+ const status = await client.runtimeOAuthCredentialStatus(workspace.config.app.code, environment);
66
71
  if (!status.configured || !status.client) {
67
72
  throw new Error("OAUTH2_RUNTIME_CLIENT_NOT_PROVISIONED");
68
73
  }
69
- const rotation = await client.stageRuntimeOAuthCredentialRotation(workspace.config.app.code, input.environmentKey, {
74
+ const rotation = await client.stageRuntimeOAuthCredentialRotation(workspace.config.app.code, environment, {
70
75
  expectedCredentialVersion: status.client.credentialVersion,
71
76
  ...(input.gracePeriodSeconds === undefined
72
77
  ? {}
73
78
  : { gracePeriodSeconds: input.gracePeriodSeconds }),
74
79
  idempotencyKey: input.idempotencyKey,
75
80
  });
76
- const head = await client.environmentHead(workspace.config.app.code, input.environmentKey);
81
+ const head = await client.environmentHead(workspace.config.app.code, environment);
77
82
  if (!head.activeAppVersionId) {
78
83
  throw new Error("OAUTH2_RUNTIME_ROTATION_ACTIVE_VERSION_REQUIRED");
79
84
  }
80
- const deployment = await client.promote({
85
+ const deployment = await client.redeploy({
81
86
  appCode: workspace.config.app.code,
82
87
  appVersionId: head.activeAppVersionId,
83
88
  ...(head.environmentId ? { environmentId: head.environmentId } : {}),
@@ -96,12 +101,22 @@ export class OpenXiangdaApplicationServices {
96
101
  }
97
102
  async createOAuthClient(root, input) {
98
103
  const workspace = await this.workspace(root);
99
- const data = await (await this.client(workspace.root)).createOAuthClient(workspace.config.app.code, input);
104
+ const environmentKey = this.deploymentEnvironment(input.environmentKey);
105
+ const data = await (await this.client(workspace.root)).createOAuthClient(workspace.config.app.code, {
106
+ ...input,
107
+ environmentKey,
108
+ });
100
109
  return this.ok("oauth.client.create", workspace.context.workspace, data);
101
110
  }
102
111
  async updateOAuthClient(root, clientId, input) {
103
112
  const workspace = await this.workspace(root);
104
- const data = await (await this.client(workspace.root)).updateOAuthClient(workspace.config.app.code, clientId, input);
113
+ const environmentKey = input.environmentKey
114
+ ? this.deploymentEnvironment(input.environmentKey)
115
+ : undefined;
116
+ const data = await (await this.client(workspace.root)).updateOAuthClient(workspace.config.app.code, clientId, {
117
+ ...input,
118
+ ...(environmentKey ? { environmentKey } : {}),
119
+ });
105
120
  return this.ok("oauth.client.update", workspace.context.workspace, data);
106
121
  }
107
122
  async rotateOAuthClientSecret(root, clientId, gracePeriodSeconds = 600) {
@@ -116,39 +131,129 @@ export class OpenXiangdaApplicationServices {
116
131
  }
117
132
  async oauthAuditEvents(root, input = {}) {
118
133
  const workspace = await this.workspace(root);
119
- const data = await (await this.client(workspace.root)).oauthAuditEvents(workspace.config.app.code, input);
134
+ const environmentKey = input.environmentKey
135
+ ? this.deploymentEnvironment(input.environmentKey)
136
+ : undefined;
137
+ const data = await (await this.client(workspace.root)).oauthAuditEvents(workspace.config.app.code, {
138
+ ...input,
139
+ ...(environmentKey ? { environmentKey } : {}),
140
+ });
120
141
  return this.ok("oauth.audit.list", workspace.context.workspace, data);
121
142
  }
122
143
  async applicationSecrets(root, environmentKey) {
123
144
  const workspace = await this.workspace(root);
124
- const data = await (await this.client(workspace.root)).applicationSecrets(workspace.config.app.code, environmentKey);
145
+ const environment = this.deploymentEnvironment(environmentKey);
146
+ const data = await (await this.client(workspace.root)).applicationSecrets(workspace.config.app.code, environment);
125
147
  return this.ok("secret.list", workspace.context.workspace, data);
126
148
  }
127
149
  async createApplicationSecret(root, environmentKey, input) {
128
150
  const workspace = await this.workspace(root);
129
- const data = await (await this.client(workspace.root)).createApplicationSecret(workspace.config.app.code, environmentKey, input);
151
+ const environment = this.deploymentEnvironment(environmentKey);
152
+ const data = await (await this.client(workspace.root)).createApplicationSecret(workspace.config.app.code, environment, input);
130
153
  return this.ok("secret.create", workspace.context.workspace, data);
131
154
  }
132
155
  async updateApplicationSecret(root, environmentKey, name, input) {
133
156
  const workspace = await this.workspace(root);
134
- const data = await (await this.client(workspace.root)).updateApplicationSecret(workspace.config.app.code, environmentKey, name, input);
157
+ const environment = this.deploymentEnvironment(environmentKey);
158
+ const data = await (await this.client(workspace.root)).updateApplicationSecret(workspace.config.app.code, environment, name, input);
135
159
  return this.ok("secret.update", workspace.context.workspace, data);
136
160
  }
137
161
  async rotateApplicationSecret(root, environmentKey, name, input) {
138
162
  const workspace = await this.workspace(root);
139
- const data = await (await this.client(workspace.root)).rotateApplicationSecret(workspace.config.app.code, environmentKey, name, input);
163
+ const environment = this.deploymentEnvironment(environmentKey);
164
+ const data = await (await this.client(workspace.root)).rotateApplicationSecret(workspace.config.app.code, environment, name, input);
140
165
  return this.ok("secret.rotate", workspace.context.workspace, data);
141
166
  }
142
167
  async deleteApplicationSecret(root, environmentKey, name, input) {
143
168
  const workspace = await this.workspace(root);
144
- const data = await (await this.client(workspace.root)).deleteApplicationSecret(workspace.config.app.code, environmentKey, name, input);
169
+ const environment = this.deploymentEnvironment(environmentKey);
170
+ const data = await (await this.client(workspace.root)).deleteApplicationSecret(workspace.config.app.code, environment, name, input);
145
171
  return this.ok("secret.delete", workspace.context.workspace, data);
146
172
  }
147
173
  async applicationSecretAuditEvents(root, environmentKey, name, limit = 100) {
148
174
  const workspace = await this.workspace(root);
149
- const data = await (await this.client(workspace.root)).applicationSecretAuditEvents(workspace.config.app.code, environmentKey, name, limit);
175
+ const environment = this.deploymentEnvironment(environmentKey);
176
+ const data = await (await this.client(workspace.root)).applicationSecretAuditEvents(workspace.config.app.code, environment, name, limit);
150
177
  return this.ok("secret.audit.list", workspace.context.workspace, data);
151
178
  }
179
+ async nativeRoleMemberships(root, environmentKey, input = {}) {
180
+ const workspace = await this.workspace(root);
181
+ const environment = this.deploymentEnvironment(environmentKey);
182
+ const client = await this.client(workspace.root);
183
+ const request = await this.nativeManagementRequest(client, workspace.config.app.code, environment);
184
+ const data = await client.nativeRoleMemberships(workspace.config.app.code, { ...input, environmentKey: environment }, request);
185
+ return this.ok("authz.membership.list", workspace.context.workspace, data);
186
+ }
187
+ async createNativeRoleMembership(root, input) {
188
+ const workspace = await this.workspace(root);
189
+ const environment = this.deploymentEnvironment(input.environmentKey);
190
+ const client = await this.client(workspace.root);
191
+ const data = await client.createNativeRoleMembership(workspace.config.app.code, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
192
+ return this.ok("authz.membership.create", workspace.context.workspace, data);
193
+ }
194
+ async updateNativeRoleMembership(root, membershipId, input) {
195
+ const workspace = await this.workspace(root);
196
+ const environment = this.deploymentEnvironment(input.environmentKey);
197
+ const client = await this.client(workspace.root);
198
+ const data = await client.updateNativeRoleMembership(workspace.config.app.code, membershipId, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
199
+ return this.ok("authz.membership.update", workspace.context.workspace, data);
200
+ }
201
+ async revokeNativeRoleMembership(root, membershipId, input) {
202
+ const workspace = await this.workspace(root);
203
+ const environment = this.deploymentEnvironment(input.environmentKey);
204
+ const client = await this.client(workspace.root);
205
+ const data = await client.revokeNativeRoleMembership(workspace.config.app.code, membershipId, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
206
+ return this.ok("authz.membership.revoke", workspace.context.workspace, data);
207
+ }
208
+ async nativeSuperAdmins(root, environmentKey, input = {}) {
209
+ const workspace = await this.workspace(root);
210
+ const environment = this.deploymentEnvironment(environmentKey);
211
+ const client = await this.client(workspace.root);
212
+ const data = await client.nativeSuperAdmins(workspace.config.app.code, input, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
213
+ return this.ok("authz.super-admin.list", workspace.context.workspace, data);
214
+ }
215
+ async grantNativeSuperAdmin(root, environmentKey, input) {
216
+ const workspace = await this.workspace(root);
217
+ const environment = this.deploymentEnvironment(environmentKey);
218
+ const client = await this.client(workspace.root);
219
+ const data = await client.grantNativeSuperAdmin(workspace.config.app.code, input, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
220
+ return this.ok("authz.super-admin.grant", workspace.context.workspace, data);
221
+ }
222
+ async revokeNativeSuperAdmin(root, environmentKey, grantId, input) {
223
+ const workspace = await this.workspace(root);
224
+ const environment = this.deploymentEnvironment(environmentKey);
225
+ const client = await this.client(workspace.root);
226
+ const data = await client.revokeNativeSuperAdmin(workspace.config.app.code, grantId, input, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
227
+ return this.ok("authz.super-admin.revoke", workspace.context.workspace, data);
228
+ }
229
+ async nativeRelationshipGrants(root, environmentKey, input = {}) {
230
+ const workspace = await this.workspace(root);
231
+ const environment = this.deploymentEnvironment(environmentKey);
232
+ const client = await this.client(workspace.root);
233
+ const data = await client.nativeRelationshipGrants(workspace.config.app.code, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
234
+ return this.ok("authz.relationship.list", workspace.context.workspace, data);
235
+ }
236
+ async createNativeRelationshipGrant(root, input) {
237
+ const workspace = await this.workspace(root);
238
+ const environment = this.deploymentEnvironment(input.environmentKey);
239
+ const client = await this.client(workspace.root);
240
+ const data = await client.createNativeRelationshipGrant(workspace.config.app.code, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
241
+ return this.ok("authz.relationship.create", workspace.context.workspace, data);
242
+ }
243
+ async updateNativeRelationshipGrant(root, grantId, input) {
244
+ const workspace = await this.workspace(root);
245
+ const environment = this.deploymentEnvironment(input.environmentKey);
246
+ const client = await this.client(workspace.root);
247
+ const data = await client.updateNativeRelationshipGrant(workspace.config.app.code, grantId, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
248
+ return this.ok("authz.relationship.update", workspace.context.workspace, data);
249
+ }
250
+ async revokeNativeRelationshipGrant(root, grantId, input) {
251
+ const workspace = await this.workspace(root);
252
+ const environment = this.deploymentEnvironment(input.environmentKey);
253
+ const client = await this.client(workspace.root);
254
+ const data = await client.revokeNativeRelationshipGrant(workspace.config.app.code, grantId, { ...input, environmentKey: environment }, await this.nativeManagementRequest(client, workspace.config.app.code, environment));
255
+ return this.ok("authz.relationship.revoke", workspace.context.workspace, data);
256
+ }
152
257
  async eventSubscriptions(root) {
153
258
  const workspace = await this.workspace(root);
154
259
  const data = await (await this.client(workspace.root)).eventSubscriptions(workspace.config.app.code);
@@ -184,32 +289,59 @@ export class OpenXiangdaApplicationServices {
184
289
  const data = await (await this.client(workspace.root)).setTimerSubscriptionStatus(workspace.config.app.code, timerId, input);
185
290
  return this.ok("event.timer.status", workspace.context.workspace, data);
186
291
  }
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
+ }
187
313
  async workflowAssigneeProviders(root, environmentKey) {
188
314
  const workspace = await this.workspace(root);
189
- const data = await (await this.client(workspace.root)).workflowAssigneeProviders(workspace.config.app.code, environmentKey);
315
+ const environment = this.deploymentEnvironment(environmentKey);
316
+ const data = await (await this.client(workspace.root)).workflowAssigneeProviders(workspace.config.app.code, environment);
190
317
  return this.ok("workflow.provider.list", workspace.context.workspace, data);
191
318
  }
192
319
  async rotateWorkflowAssigneeProviderSecret(root, providerCode, input) {
193
320
  const workspace = await this.workspace(root);
194
- const data = await (await this.client(workspace.root)).rotateWorkflowAssigneeProviderSecret(workspace.config.app.code, providerCode, input);
321
+ const environmentKey = this.deploymentEnvironment(input.environmentKey);
322
+ const data = await (await this.client(workspace.root)).rotateWorkflowAssigneeProviderSecret(workspace.config.app.code, providerCode, { ...input, environmentKey });
195
323
  return this.ok("workflow.provider.rotate", workspace.context.workspace, data);
196
324
  }
197
325
  async linkApplication(root, input) {
198
326
  const workspace = await this.workspace(root);
199
327
  const path = join(workspace.root, ".openxiangda", "link.json");
328
+ const environments = (input.environments || []).map((environment) => ({
329
+ ...environment,
330
+ kind: this.deploymentEnvironment(environment.kind),
331
+ }));
200
332
  mkdirSync(dirname(path), { recursive: true });
201
333
  const value = {
202
334
  schemaVersion: 2,
203
335
  appCode: workspace.config.app.code,
204
336
  baseUrl: normalizePlatformBaseUrl(input.baseUrl),
205
- environments: input.environments || [],
337
+ environments,
206
338
  };
207
339
  writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
208
340
  return this.ok("app.link", workspace.context.workspace, { path, ...value });
209
341
  }
210
342
  async generate(input = {}) {
211
343
  const workspace = await this.workspace(input.root);
212
- const sources = compileApplicationSources(workspace.config);
344
+ const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
213
345
  const output = join(workspace.root, "packages/contracts/src/generated.ts");
214
346
  const current = existsSync(output) ? readFileSync(output, "utf8") : "";
215
347
  const changed = current !== sources.contracts.typescript;
@@ -296,7 +428,7 @@ export class OpenXiangdaApplicationServices {
296
428
  diagnostics.push(this.diagnostic("BACKEND_IMAGE_NOT_IMMUTABLE", "后端镜像必须使用不可变 sha256 digest,不能使用可变 tag", "backend.image", "将镜像引用改为 <registry/repository@sha256:64位摘要>"));
297
429
  return this.result("build", workspace.context.workspace, undefined, diagnostics);
298
430
  }
299
- const sources = compileApplicationSources(workspace.config);
431
+ const sources = compileApplicationSources(workspace.config, this.toolchainVersion);
300
432
  const frontendContent = this.directoryBundle(join(workspace.root, workspace.config.frontend.root, "dist"));
301
433
  const frontendDigest = sha256Bytes(frontendContent);
302
434
  const backendContent = canonicalJson({ image: backendImage });
@@ -342,7 +474,7 @@ export class OpenXiangdaApplicationServices {
342
474
  config: sources.config.digest,
343
475
  dataContract: sources.contracts.digest,
344
476
  },
345
- minimumPlatformVersion: "2.0.0-alpha.1",
477
+ minimumPlatformVersion: "2.0.0-alpha.2",
346
478
  metadata: {
347
479
  runtimeProfile: workspace.config.backend.runMode || "shared",
348
480
  },
@@ -392,9 +524,9 @@ export class OpenXiangdaApplicationServices {
392
524
  }
393
525
  async deploy(input) {
394
526
  const workspace = await this.workspace(input.root);
395
- if (input.environment === "production") {
527
+ if (String(input.environment) !== "preproduction") {
396
528
  return this.result("deploy", workspace.context.workspace, undefined, [
397
- this.diagnostic("PRODUCTION_PROMOTION_REQUIRED", "生产环境不接受本地重新构建的 AppPackage", "production", "先执行 openxiangda deploy preproduction,成功后使用 openxiangda promote <deploymentId> production"),
529
+ this.diagnostic("DEPLOY_PREPRODUCTION_ONLY", "Native 2.0 只允许直接部署到预发环境", String(input.environment), "使用 openxiangda deploy preproduction;生产环境必须晋级同一 AppVersion"),
398
530
  ]);
399
531
  }
400
532
  const built = await this.buildSealed({ ...input, root: workspace.root });
@@ -455,16 +587,17 @@ export class OpenXiangdaApplicationServices {
455
587
  }
456
588
  async promote(root, deploymentId, environment) {
457
589
  const workspace = await this.workspace(root);
590
+ if (String(environment) !== "production") {
591
+ throw new Error(`OPENXIANGDA_PROMOTION_ENVIRONMENT_INVALID: ${String(environment)}`);
592
+ }
458
593
  const client = await this.client(workspace.root);
459
594
  const source = await client.deployment(workspace.config.app.code, deploymentId);
460
595
  const appVersionId = String(source.result?.applicationVersionId || "");
461
596
  if (!appVersionId)
462
597
  throw new Error("DEPLOYMENT_APP_VERSION_NOT_AVAILABLE");
463
- if (environment === "production") {
464
- const diagnostics = await this.productionPromotionDiagnostics(workspace, client, source, appVersionId);
465
- if (diagnostics.length > 0) {
466
- return this.result("promote", workspace.context.workspace, undefined, diagnostics);
467
- }
598
+ const diagnostics = await this.productionPromotionDiagnostics(workspace, client, source, appVersionId);
599
+ if (diagnostics.length > 0) {
600
+ return this.result("promote", workspace.context.workspace, undefined, diagnostics);
468
601
  }
469
602
  const deployment = await client.promote({
470
603
  appCode: workspace.config.app.code,
@@ -523,14 +656,22 @@ export class OpenXiangdaApplicationServices {
523
656
  }
524
657
  async rollback(root, environment, appVersionId) {
525
658
  const workspace = await this.workspace(root);
659
+ const targetEnvironment = this.deploymentEnvironment(environment);
526
660
  const deployment = await (await this.client(workspace.root)).rollback({
527
661
  appCode: workspace.config.app.code,
528
662
  appVersionId,
529
- environmentKind: environment,
530
- idempotencyKey: `rollback:${appVersionId}:${environment}`,
663
+ environmentKind: targetEnvironment,
664
+ idempotencyKey: `rollback:${appVersionId}:${targetEnvironment}`,
531
665
  });
532
666
  return this.ok("rollback", workspace.context.workspace, deployment);
533
667
  }
668
+ deploymentEnvironment(value) {
669
+ const normalized = String(value || "").trim();
670
+ if (normalized === "preproduction" || normalized === "production") {
671
+ return normalized;
672
+ }
673
+ throw new Error(`OPENXIANGDA_REMOTE_ENVIRONMENT_INVALID: ${normalized || "<empty>"}`);
674
+ }
534
675
  async doctor(root) {
535
676
  const workspace = await this.workspace(root);
536
677
  const commands = ["git", "node", "pnpm", "docker"].map((command) => {
@@ -541,6 +682,16 @@ export class OpenXiangdaApplicationServices {
541
682
  version: String(result.stdout || result.stderr || "").trim(),
542
683
  };
543
684
  });
685
+ const dockerInfo = spawnSync("docker", ["info", "--format", "{{.ServerVersion}}"], { encoding: "utf8" });
686
+ const containerRuntime = {
687
+ requiredFor: "openxiangda dev",
688
+ ok: dockerInfo.status === 0,
689
+ serverVersion: String(dockerInfo.stdout || "").trim() || null,
690
+ message: dockerInfo.status === 0
691
+ ? null
692
+ : String(dockerInfo.stderr || dockerInfo.stdout || "").trim() ||
693
+ "Docker daemon is unavailable",
694
+ };
544
695
  let platform;
545
696
  try {
546
697
  platform = (await (await this.client(workspace.root)).capabilities());
@@ -549,23 +700,86 @@ export class OpenXiangdaApplicationServices {
549
700
  platform = { ok: false, message: error.message };
550
701
  }
551
702
  const diagnostics = commands
552
- .filter((item) => !item.ok && item.command !== "docker")
703
+ .filter((item) => !item.ok)
553
704
  .map((item) => this.diagnostic("TOOLCHAIN_COMMAND_MISSING", `缺少必需命令: ${item.command}`, item.command));
554
- return this.result("doctor", workspace.context.workspace, { commands, platform }, diagnostics);
705
+ if (!containerRuntime.ok &&
706
+ commands.find(item => item.command === "docker")?.ok) {
707
+ diagnostics.push(this.diagnostic("LOCAL_CONTAINER_RUNTIME_UNAVAILABLE", "Docker 已安装但容器运行时不可用,完整本地开发无法启动 PostgreSQL", "docker.info", "启动 Docker Desktop(或兼容 Docker daemon)后重新执行 openxiangda doctor"));
708
+ }
709
+ return this.result("doctor", workspace.context.workspace, {
710
+ commands,
711
+ containerRuntime,
712
+ localDevelopment: localDevelopmentStatus(workspace.root),
713
+ platform,
714
+ }, diagnostics);
555
715
  }
556
- async dev(root) {
716
+ async devStatus(root) {
557
717
  const workspace = await this.workspace(root);
558
- return await new Promise((resolve) => {
559
- const child = spawn("pnpm", ["run", "dev"], {
560
- cwd: workspace.root,
561
- stdio: "inherit",
562
- });
563
- child.on("exit", (code) => resolve(this.result("dev", workspace.context.workspace, { exitCode: code ?? 1 }, code === 0
564
- ? []
565
- : [
566
- this.diagnostic("DEV_PROCESS_FAILED", "开发进程异常退出", "dev"),
567
- ])));
718
+ return this.ok("dev.status", workspace.context.workspace, localDevelopmentStatus(workspace.root));
719
+ }
720
+ async devStop(root) {
721
+ const workspace = await this.workspace(root);
722
+ return this.ok("dev.stop", workspace.context.workspace, await stopLocalDevelopment(workspace.root));
723
+ }
724
+ async devResetData(root) {
725
+ const workspace = await this.workspace(root);
726
+ return this.ok("dev.reset", workspace.context.workspace, await resetLocalDevelopmentData(workspace.root));
727
+ }
728
+ async dev(root, input = {}) {
729
+ const workspace = await this.workspace(root);
730
+ const generated = await this.generate({
731
+ root: workspace.root,
568
732
  });
733
+ if (!generated.ok) {
734
+ return this.result("dev", workspace.context.workspace, undefined, generated.diagnostics, generated.nextActions);
735
+ }
736
+ const localFixturePath = join(workspace.root, "openxiangda.local.json");
737
+ let localFixture;
738
+ if (existsSync(localFixturePath)) {
739
+ try {
740
+ localFixture = JSON.parse(readFileSync(localFixturePath, "utf8"));
741
+ }
742
+ catch (error) {
743
+ return this.result("dev", workspace.context.workspace, undefined, [
744
+ this.diagnostic("LOCAL_DEVELOPMENT_FIXTURE_INVALID", `本地开发夹具不是有效 JSON: ${error.message}`, localFixturePath, "修复 openxiangda.local.json 后重试"),
745
+ ]);
746
+ }
747
+ }
748
+ const local = await runLocalDevelopment({
749
+ root: workspace.root,
750
+ appCode: workspace.config.app.code,
751
+ runtimeOAuthScopes: [
752
+ ...new Set((workspace.config.authz?.roles || []).flatMap(role => role.capabilities || [])),
753
+ ],
754
+ eventSubscriptionCodes: (workspace.config.events?.subscriptions || []).map(subscription => subscription.code),
755
+ localManifest: {
756
+ schemaVersion: 1,
757
+ appCode: workspace.config.app.code,
758
+ resources: workspace.config.data?.resources || [],
759
+ roles: workspace.config.authz?.roles || [],
760
+ scopeDimensions: workspace.config.authz?.scopeDimensions || [],
761
+ dataPolicies: workspace.config.authz?.dataPolicies || [],
762
+ backendSecrets: workspace.config.backend?.secrets || [],
763
+ eventSubscriptions: workspace.config.events?.subscriptions || [],
764
+ timerSubscriptions: workspace.config.events?.timers || [],
765
+ workflowDefinitions: workspace.config.workflows?.definitions || [],
766
+ workflowBindings: workspace.config.workflows?.bindings || [],
767
+ workflowActivations: workspace.config.workflows?.activations || [],
768
+ workflowProviders: workspace.config.workflows?.providers || [],
769
+ ...(localFixture ? { fixture: localFixture } : {}),
770
+ },
771
+ ...(input.noOpen === undefined ? {} : { noOpen: input.noOpen }),
772
+ ...(input.reset === undefined ? {} : { reset: input.reset }),
773
+ ...(input.uiOnly === undefined ? {} : { uiOnly: input.uiOnly }),
774
+ ...(input.webPort === undefined ? {} : { webPort: input.webPort }),
775
+ ...(input.onStatus ? { onStatus: input.onStatus } : {}),
776
+ });
777
+ const diagnostics = local.exitCode === 0
778
+ ? []
779
+ : [
780
+ this.diagnostic("DEV_PROCESS_FAILED", "本地开发进程异常退出", local.logPath, "查看本地诊断和日志后重试", { diagnosticsPath: local.diagnosticsPath }),
781
+ ];
782
+ return this.result("dev", workspace.context.workspace, local, diagnostics);
569
783
  }
570
784
  async workspace(root) {
571
785
  return await loadWorkspace(root, this.toolchainVersion);
@@ -592,6 +806,27 @@ export class OpenXiangdaApplicationServices {
592
806
  tokenProvider: session,
593
807
  });
594
808
  }
809
+ async nativeManagementRequest(client, appCode, environmentKey) {
810
+ const context = await client.roleSessionContext(appCode, environmentKey);
811
+ if (context.currentRoleSubject?.subjectKind === "super_admin" &&
812
+ context.roleSession) {
813
+ return { roleSessionId: context.roleSession.id };
814
+ }
815
+ const superAdmin = context.roleSubjectPage.items.find((item) => item.subjectKind === "super_admin");
816
+ if (superAdmin) {
817
+ const selected = await client.switchRoleSessionContext(appCode, {
818
+ targetRoleSubjectKey: superAdmin.subjectKey,
819
+ expectedRoleSessionId: context.roleSession?.id || null,
820
+ environmentKey,
821
+ });
822
+ if (selected.roleSession) {
823
+ return { roleSessionId: selected.roleSession.id };
824
+ }
825
+ }
826
+ if (context.roleSession)
827
+ return { roleSessionId: context.roleSession.id };
828
+ throw new Error("OPENXIANGDA_NATIVE_AUTHZ_MANAGEMENT_ROLE_REQUIRED");
829
+ }
595
830
  get toolchainVersion() {
596
831
  return this.options.toolchainVersion || OPENXIANGDA_TOOLCHAIN_VERSION;
597
832
  }