openxiangda-devkit-core 2.0.0-alpha.16 → 2.0.0-alpha.17
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/dist/application-services.d.ts +97 -13
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +285 -51
- package/dist/application-services.js.map +1 -1
- package/dist/command-registry.d.ts +152 -2
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +91 -1
- package/dist/command-registry.js.map +1 -1
- package/dist/control-plane-client.d.ts +141 -3
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +137 -1
- package/dist/control-plane-client.js.map +1 -1
- package/dist/deployment.d.ts +2 -2
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +5 -2
- package/dist/deployment.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/local-development.d.ts +84 -0
- package/dist/local-development.d.ts.map +1 -0
- package/dist/local-development.js +796 -0
- package/dist/local-development.js.map +1 -0
- package/dist/local-postgres.d.ts +32 -0
- package/dist/local-postgres.d.ts.map +1 -0
- package/dist/local-postgres.js +317 -0
- package/dist/local-postgres.js.map +1 -0
- package/package.json +3 -3
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
527
|
+
if (String(input.environment) !== "preproduction") {
|
|
396
528
|
return this.result("deploy", workspace.context.workspace, undefined, [
|
|
397
|
-
this.diagnostic("
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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:
|
|
530
|
-
idempotencyKey: `rollback:${appVersionId}:${
|
|
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,85 @@ export class OpenXiangdaApplicationServices {
|
|
|
549
700
|
platform = { ok: false, message: error.message };
|
|
550
701
|
}
|
|
551
702
|
const diagnostics = commands
|
|
552
|
-
.filter((item) => !item.ok
|
|
703
|
+
.filter((item) => !item.ok)
|
|
553
704
|
.map((item) => this.diagnostic("TOOLCHAIN_COMMAND_MISSING", `缺少必需命令: ${item.command}`, item.command));
|
|
554
|
-
|
|
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
|
|
716
|
+
async devStatus(root) {
|
|
557
717
|
const workspace = await this.workspace(root);
|
|
558
|
-
return
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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
|
+
eventSubscriptions: workspace.config.events?.subscriptions || [],
|
|
763
|
+
timerSubscriptions: workspace.config.events?.timers || [],
|
|
764
|
+
workflowDefinitions: workspace.config.workflows?.definitions || [],
|
|
765
|
+
workflowBindings: workspace.config.workflows?.bindings || [],
|
|
766
|
+
workflowActivations: workspace.config.workflows?.activations || [],
|
|
767
|
+
workflowProviders: workspace.config.workflows?.providers || [],
|
|
768
|
+
...(localFixture ? { fixture: localFixture } : {}),
|
|
769
|
+
},
|
|
770
|
+
...(input.noOpen === undefined ? {} : { noOpen: input.noOpen }),
|
|
771
|
+
...(input.reset === undefined ? {} : { reset: input.reset }),
|
|
772
|
+
...(input.uiOnly === undefined ? {} : { uiOnly: input.uiOnly }),
|
|
773
|
+
...(input.webPort === undefined ? {} : { webPort: input.webPort }),
|
|
774
|
+
...(input.onStatus ? { onStatus: input.onStatus } : {}),
|
|
775
|
+
});
|
|
776
|
+
const diagnostics = local.exitCode === 0
|
|
777
|
+
? []
|
|
778
|
+
: [
|
|
779
|
+
this.diagnostic("DEV_PROCESS_FAILED", "本地开发进程异常退出", local.logPath, "查看本地诊断和日志后重试", { diagnosticsPath: local.diagnosticsPath }),
|
|
780
|
+
];
|
|
781
|
+
return this.result("dev", workspace.context.workspace, local, diagnostics);
|
|
569
782
|
}
|
|
570
783
|
async workspace(root) {
|
|
571
784
|
return await loadWorkspace(root, this.toolchainVersion);
|
|
@@ -592,6 +805,27 @@ export class OpenXiangdaApplicationServices {
|
|
|
592
805
|
tokenProvider: session,
|
|
593
806
|
});
|
|
594
807
|
}
|
|
808
|
+
async nativeManagementRequest(client, appCode, environmentKey) {
|
|
809
|
+
const context = await client.roleSessionContext(appCode, environmentKey);
|
|
810
|
+
if (context.currentRoleSubject?.subjectKind === "super_admin" &&
|
|
811
|
+
context.roleSession) {
|
|
812
|
+
return { roleSessionId: context.roleSession.id };
|
|
813
|
+
}
|
|
814
|
+
const superAdmin = context.roleSubjectPage.items.find((item) => item.subjectKind === "super_admin");
|
|
815
|
+
if (superAdmin) {
|
|
816
|
+
const selected = await client.switchRoleSessionContext(appCode, {
|
|
817
|
+
targetRoleSubjectKey: superAdmin.subjectKey,
|
|
818
|
+
expectedRoleSessionId: context.roleSession?.id || null,
|
|
819
|
+
environmentKey,
|
|
820
|
+
});
|
|
821
|
+
if (selected.roleSession) {
|
|
822
|
+
return { roleSessionId: selected.roleSession.id };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (context.roleSession)
|
|
826
|
+
return { roleSessionId: context.roleSession.id };
|
|
827
|
+
throw new Error("OPENXIANGDA_NATIVE_AUTHZ_MANAGEMENT_ROLE_REQUIRED");
|
|
828
|
+
}
|
|
595
829
|
get toolchainVersion() {
|
|
596
830
|
return this.options.toolchainVersion || OPENXIANGDA_TOOLCHAIN_VERSION;
|
|
597
831
|
}
|