create-windy 0.3.0 → 0.3.2

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 (40) hide show
  1. package/README.md +30 -3
  2. package/dist/cli.js +7640 -3255
  3. package/package.json +3 -3
  4. package/template/.windy-template.json +2 -2
  5. package/template/README.md +25 -3
  6. package/template/_gitignore +4 -0
  7. package/template/apps/agent-server/tests/__init__.py +1 -0
  8. package/template/apps/agent-server/tests/openai_provider_test_support.py +131 -0
  9. package/template/apps/agent-server/tests/test_openai_provider.py +7 -123
  10. package/template/apps/server/src/auth/routes.test-support.ts +94 -0
  11. package/template/apps/server/src/auth/routes.test.ts +20 -88
  12. package/template/apps/server/src/index.ts +17 -34
  13. package/template/apps/server/src/mcp/adapter.test.ts +3 -2
  14. package/template/apps/server/src/mcp/adapter.ts +9 -9
  15. package/template/apps/server/src/mcp/routes.ts +4 -3
  16. package/template/apps/server/src/platform-http-runtime.ts +87 -0
  17. package/template/apps/server/src/system/drizzle-system-configs.ts +319 -0
  18. package/template/apps/server/src/system/drizzle-system.ts +35 -315
  19. package/template/apps/server/src/system/resource-routes.ts +270 -0
  20. package/template/apps/server/src/system/routes.ts +3 -269
  21. package/template/apps/web/src/pages/system/components/SystemResourceLoadError.vue +21 -0
  22. package/template/apps/web/src/pages/system/components/SystemResourceLoadError.webtest.ts +17 -0
  23. package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +5 -10
  24. package/template/docs/architecture/mcp-business-capabilities.md +107 -0
  25. package/template/docs/development/custom-login-page.md +3 -3
  26. package/template/docs/platform/agent-runtime.md +5 -5
  27. package/template/docs/platform/agent-tools.md +4 -0
  28. package/template/packages/modules/package.json +1 -1
  29. package/template/packages/modules/src/composition-fixtures.test-support.ts +164 -0
  30. package/template/packages/modules/src/composition.test.ts +5 -158
  31. package/template/packages/modules/src/system-api-operational.ts +234 -0
  32. package/template/packages/modules/src/system-api-permissions.ts +8 -229
  33. package/template/packages/modules/src/system-module-tasks.ts +86 -0
  34. package/template/packages/modules/src/system-modules-access.test.ts +60 -0
  35. package/template/packages/modules/src/system-modules.test.ts +0 -53
  36. package/template/packages/modules/src/system-modules.ts +2 -84
  37. package/template/packages/shared/index.ts +1 -0
  38. package/template/packages/shared/package.json +1 -1
  39. package/template/packages/shared/src/mcp.test.ts +12 -0
  40. package/template/packages/shared/src/mcp.ts +7 -0
@@ -26,10 +26,7 @@ import { GovernedToolRegistry } from "./agent/tool-registry.js";
26
26
  import { PlatformMcpAdapter } from "./mcp/adapter.js";
27
27
  import { registerMcpRoutes } from "./mcp/routes.js";
28
28
  // @windy-module system.agent end
29
- import { registerAuthRoutes, registerAuthSessionRoute } from "./auth/routes.js";
30
29
  import { createAuthRuntime } from "./auth/runtime.js";
31
- import { createPlatformHttpApp } from "./http-app.js";
32
- import { bootstrapAdminPassword } from "./auth/bootstrap-admin.js";
33
30
  import { createServerPersistence } from "./persistence.js";
34
31
  // @windy-module system.license begin
35
32
  import { registerPlatformLicenseRoutes } from "./license/routes.js";
@@ -51,7 +48,6 @@ import { SchedulerWorker } from "./scheduler/worker.js";
51
48
  // @windy-module system.settings begin
52
49
  import { createPlatformSettingsRuntime } from "./settings/runtime.js";
53
50
  // @windy-module system.settings end
54
- import { registerProfileRoutes } from "./profile/routes.js";
55
51
  // @windy-module system.notification begin
56
52
  import { registerNotificationRoutes } from "./notification/routes.js";
57
53
  // @windy-module system.notification end
@@ -76,11 +72,13 @@ import { createSearchHost } from "./search/host.js";
76
72
  // @windy-module system.workflow begin
77
73
  import { registerWorkflowRoutes } from "./workflow/routes.js";
78
74
  // @windy-module system.workflow end
79
- import {
80
- createPlatformObservability,
81
- logPlatformStartup,
82
- } from "./observability/bootstrap.js";
83
75
  import { bootstrapServerModules } from "./module-bootstrap.js";
76
+ import {
77
+ createHttpObservability,
78
+ createPlatformServerApp,
79
+ listenPlatformServer,
80
+ logServerStartup,
81
+ } from "./platform-http-runtime.js";
84
82
 
85
83
  const logger = pino({ name: "windy-server" });
86
84
  const platformConfig = loadPlatformConfig();
@@ -128,14 +126,7 @@ const runtime = createServerRuntime(process.env, {
128
126
  auditWriter: persistence?.auditWriter,
129
127
  // @windy-module system.audit end
130
128
  });
131
- const observability = createPlatformObservability({
132
- databaseCheck: persistence
133
- ? async () => void (await persistence.pool.query("select 1"))
134
- : undefined,
135
- databaseRequired: process.env.NODE_ENV === "production",
136
- auditStatus: () => runtime.auditSink.status(),
137
- logger,
138
- });
129
+ const observability = createHttpObservability(runtime, persistence, logger);
139
130
  // @windy-module system.license begin
140
131
  platformLicenseRuntime.setAuditSink(runtime.auditSink);
141
132
  // @windy-module system.license end
@@ -208,20 +199,17 @@ const schedulerWorker = new SchedulerWorker(
208
199
  (result) => observability.metrics.observeSchedulerTick(result),
209
200
  );
210
201
  // @windy-module system.scheduler end
211
- await bootstrapAdminPassword({
202
+ const app = await createPlatformServerApp({
203
+ runtime,
204
+ persistence,
205
+ platformConfig,
206
+ authRepository,
212
207
  authService,
213
- authRepository: persistence?.authRepository,
214
208
  repositories: systemRepositories,
215
- password: process.env.WINDY_BOOTSTRAP_ADMIN_PASSWORD,
216
- });
217
- const app = createPlatformHttpApp(runtime, observability);
218
- observability.register(app);
219
- registerAuthSessionRoute(app);
220
- registerAuthRoutes(app, authService, {
221
- sessionCookieName: platformConfig.security.sessionCookieName,
222
- secureCookie: process.env.NODE_ENV === "production",
209
+ features,
210
+ logger,
211
+ observability,
223
212
  });
224
- registerProfileRoutes(app, authRepository, runtime, features);
225
213
 
226
214
  // @windy-module system.license begin
227
215
  registerPlatformLicenseRoutes(app, platformLicenseRuntime);
@@ -354,13 +342,8 @@ validateInstalledRuntime({
354
342
  hosts: moduleHosts,
355
343
  });
356
344
 
357
- const port = process.env.PORT ? Number(process.env.PORT) : 9_747;
358
- app.listen(port);
345
+ const port = listenPlatformServer(app);
359
346
  // @windy-module system.scheduler begin
360
347
  schedulerWorker.start();
361
348
  // @windy-module system.scheduler end
362
- logPlatformStartup(logger, {
363
- featureSync,
364
- persistenceEnabled: Boolean(persistence),
365
- port,
366
- });
349
+ logServerStartup(logger, featureSync, Boolean(persistence), port);
@@ -1,6 +1,7 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
3
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
+ import { MCP_API_PATH } from "@southwind-ai/shared";
4
5
  import { createFoundationSnapshot, loadSystemModules } from "../foundation.js";
5
6
  import { createServerRuntime } from "../runtime.js";
6
7
  import { createSystemRepositories } from "../system/seed.js";
@@ -150,7 +151,7 @@ async function connectClient(
150
151
  token: string,
151
152
  ): Promise<Client> {
152
153
  const transport = new StreamableHTTPClientTransport(
153
- new URL("http://localhost/api/mcp"),
154
+ new URL(`http://localhost${MCP_API_PATH}`),
154
155
  {
155
156
  requestInit: {
156
157
  headers: { authorization: `Bearer ${token}` },
@@ -175,7 +176,7 @@ function mcpRequest(token?: string, origin?: string): Request {
175
176
  });
176
177
  if (token) headers.set("authorization", `Bearer ${token}`);
177
178
  if (origin) headers.set("origin", origin);
178
- return new Request("http://localhost/api/mcp", {
179
+ return new Request(`http://localhost${MCP_API_PATH}`, {
179
180
  method: "POST",
180
181
  headers,
181
182
  body: JSON.stringify({
@@ -10,7 +10,10 @@ import type {
10
10
  ModuleToolInput,
11
11
  ModuleToolResult,
12
12
  } from "@southwind-ai/server-sdk";
13
- import type { FeatureFlagDefinition } from "@southwind-ai/shared";
13
+ import {
14
+ MCP_SERVER_INFO,
15
+ type FeatureFlagDefinition,
16
+ } from "@southwind-ai/shared";
14
17
  import { evaluateAccessGuards } from "../access-guards.js";
15
18
  import type { RequestGuardContext } from "../guards.js";
16
19
  import { findFeature } from "../route-guards.js";
@@ -69,14 +72,11 @@ export class PlatformMcpAdapter {
69
72
  }
70
73
 
71
74
  private createServer(context: RequestGuardContext): Server {
72
- const server = new Server(
73
- { name: "windy-platform", version: "0.1.0" },
74
- {
75
- capabilities: { tools: {} },
76
- instructions:
77
- "仅调用当前账号可见的只读业务工具;权限和数据范围由 Windy 平台逐次校验。",
78
- },
79
- );
75
+ const server = new Server(MCP_SERVER_INFO, {
76
+ capabilities: { tools: {} },
77
+ instructions:
78
+ "仅调用当前账号可见的只读业务工具;权限和数据范围由 Windy 平台逐次校验。",
79
+ });
80
80
 
81
81
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
82
82
  tools: await this.allowedQueryTools(context),
@@ -1,3 +1,4 @@
1
+ import { MCP_ROUTE_PATH } from "@southwind-ai/shared";
1
2
  import type { RouteApp } from "../system/routes.js";
2
3
  import { PlatformMcpAdapter } from "./adapter.js";
3
4
 
@@ -15,7 +16,7 @@ export function registerMcpRoutes(
15
16
  const context = rawContext as McpRouteContext;
16
17
  return adapter.handle(context.request, context.guardContext, context.body);
17
18
  };
18
- app.get("/mcp", handle);
19
- app.post("/mcp", handle);
20
- app.delete("/mcp", handle);
19
+ app.get(MCP_ROUTE_PATH, handle);
20
+ app.post(MCP_ROUTE_PATH, handle);
21
+ app.delete(MCP_ROUTE_PATH, handle);
21
22
  }
@@ -0,0 +1,87 @@
1
+ import type { PlatformConfig } from "@southwind-ai/config";
2
+ import type { FeatureFlagDefinition } from "@southwind-ai/shared";
3
+ import type { AuthRepository } from "./auth/repository.js";
4
+ import type { AuthService } from "./auth/service.js";
5
+ import { bootstrapAdminPassword } from "./auth/bootstrap-admin.js";
6
+ import { registerAuthRoutes, registerAuthSessionRoute } from "./auth/routes.js";
7
+ import { createPlatformHttpApp } from "./http-app.js";
8
+ import {
9
+ createPlatformObservability,
10
+ logPlatformStartup,
11
+ } from "./observability/bootstrap.js";
12
+ import type { StructuredRequestLogger } from "./observability/http-observability.js";
13
+ import type { createServerPersistence } from "./persistence.js";
14
+ import { registerProfileRoutes } from "./profile/routes.js";
15
+ import type { createServerRuntime } from "./runtime.js";
16
+ import type { SystemRepositories } from "./system/seed.js";
17
+ import type { FeatureManifestSyncResult } from "./feature/manifest-feature-repository.js";
18
+
19
+ interface CreatePlatformServerAppInput {
20
+ runtime: ReturnType<typeof createServerRuntime>;
21
+ persistence: ReturnType<typeof createServerPersistence>;
22
+ platformConfig: PlatformConfig;
23
+ authRepository: AuthRepository;
24
+ authService: AuthService;
25
+ repositories: SystemRepositories;
26
+ features: FeatureFlagDefinition[];
27
+ logger: StructuredRequestLogger;
28
+ observability: ReturnType<typeof createPlatformObservability>;
29
+ }
30
+
31
+ export function createHttpObservability(
32
+ runtime: ReturnType<typeof createServerRuntime>,
33
+ persistence: ReturnType<typeof createServerPersistence>,
34
+ logger: StructuredRequestLogger,
35
+ ) {
36
+ const database = persistence;
37
+ return createPlatformObservability({
38
+ databaseCheck: database
39
+ ? async () => void (await database.pool.query("select 1"))
40
+ : undefined,
41
+ databaseRequired: process.env.NODE_ENV === "production",
42
+ auditStatus: () => runtime.auditSink.status(),
43
+ logger,
44
+ });
45
+ }
46
+
47
+ export async function createPlatformServerApp(
48
+ input: CreatePlatformServerAppInput,
49
+ ) {
50
+ await bootstrapAdminPassword({
51
+ authService: input.authService,
52
+ authRepository: input.persistence?.authRepository,
53
+ repositories: input.repositories,
54
+ password: process.env.WINDY_BOOTSTRAP_ADMIN_PASSWORD,
55
+ });
56
+ const app = createPlatformHttpApp(input.runtime, input.observability);
57
+ input.observability.register(app);
58
+ registerAuthSessionRoute(app);
59
+ registerAuthRoutes(app, input.authService, {
60
+ sessionCookieName: input.platformConfig.security.sessionCookieName,
61
+ secureCookie: process.env.NODE_ENV === "production",
62
+ });
63
+ registerProfileRoutes(
64
+ app,
65
+ input.authRepository,
66
+ input.runtime,
67
+ input.features,
68
+ );
69
+ return app;
70
+ }
71
+
72
+ export function listenPlatformServer(
73
+ app: ReturnType<typeof createPlatformHttpApp>,
74
+ ): number {
75
+ const port = process.env.PORT ? Number(process.env.PORT) : 9_747;
76
+ app.listen(port);
77
+ return port;
78
+ }
79
+
80
+ export function logServerStartup(
81
+ logger: StructuredRequestLogger,
82
+ featureSync: FeatureManifestSyncResult,
83
+ persistenceEnabled: boolean,
84
+ port: number,
85
+ ): void {
86
+ logPlatformStartup(logger, { featureSync, persistenceEnabled, port });
87
+ }
@@ -0,0 +1,319 @@
1
+ import {
2
+ departments,
3
+ dictionaries,
4
+ featureFlags,
5
+ // @windy-module system.license begin
6
+ legacyLicenses,
7
+ // @windy-module system.license end
8
+ menus,
9
+ roles,
10
+ users,
11
+ } from "@southwind-ai/database";
12
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
13
+ import type {
14
+ ManagedDepartment,
15
+ ManagedDictionary,
16
+ ManagedFeature,
17
+ // @windy-module system.license begin
18
+ ManagedLicense,
19
+ // @windy-module system.license end
20
+ ManagedMenu,
21
+ ManagedRole,
22
+ ManagedUser,
23
+ } from "./entities.js";
24
+ import {
25
+ array,
26
+ auditEntity,
27
+ auditRow,
28
+ // @windy-module system.license begin
29
+ date,
30
+ iso,
31
+ optionalIso,
32
+ // @windy-module system.license end
33
+ pick,
34
+ } from "./drizzle-mapping.js";
35
+ import {
36
+ DrizzleEntityRepository,
37
+ type DrizzleEntityRepositoryConfig,
38
+ } from "./drizzle-repository.js";
39
+ import type { DrizzleScopedEntityRepositoryConfig } from "./drizzle-scoped-repository.js";
40
+ import type { DrizzleTransactionContext } from "./drizzle-transaction-context.js";
41
+ import { userDataScopeBinding } from "./user-data-scope.js";
42
+
43
+ type SystemEntity =
44
+ | ManagedUser
45
+ | ManagedRole
46
+ | ManagedDepartment
47
+ | ManagedMenu
48
+ | ManagedDictionary
49
+ | ManagedFeature
50
+ // @windy-module system.license begin
51
+ | ManagedLicense;
52
+ // @windy-module system.license end
53
+
54
+ export function createSystemEntityRepository<T extends SystemEntity>(
55
+ db: NodePgDatabase,
56
+ config: DrizzleEntityRepositoryConfig<T>,
57
+ transactionContext: DrizzleTransactionContext,
58
+ ) {
59
+ return new DrizzleEntityRepository<T>(db, config, transactionContext);
60
+ }
61
+
62
+ export function userConfig(
63
+ seedItems: ManagedUser[],
64
+ ): DrizzleScopedEntityRepositoryConfig<ManagedUser> {
65
+ return {
66
+ table: users,
67
+ idColumn: "id",
68
+ seedItems,
69
+ dataScope: {
70
+ drizzle: {
71
+ ownerId: users.id,
72
+ tenant: { mode: "fixed", tenantId: "single-tenant" },
73
+ dimensions: { departmentId: users.departmentId },
74
+ },
75
+ entity: userDataScopeBinding,
76
+ },
77
+ toRow: (item) => ({
78
+ ...auditRow(item),
79
+ ...pick(item, [
80
+ "id",
81
+ "username",
82
+ "displayName",
83
+ "email",
84
+ "phone",
85
+ "departmentId",
86
+ "roleIds",
87
+ "postIds",
88
+ "status",
89
+ ]),
90
+ }),
91
+ toEntity: (row) =>
92
+ ({
93
+ ...auditEntity(row),
94
+ ...pick(row, [
95
+ "id",
96
+ "username",
97
+ "displayName",
98
+ "email",
99
+ "phone",
100
+ "departmentId",
101
+ "postIds",
102
+ "status",
103
+ ]),
104
+ roleIds: array(row.roleIds),
105
+ }) as ManagedUser,
106
+ };
107
+ }
108
+
109
+ export function roleConfig(
110
+ seedItems: ManagedRole[],
111
+ ): DrizzleEntityRepositoryConfig<ManagedRole> {
112
+ return {
113
+ table: roles,
114
+ idColumn: "id",
115
+ seedItems,
116
+ toRow: (item) => ({
117
+ ...auditRow(item),
118
+ ...pick(item, [
119
+ "id",
120
+ "code",
121
+ "name",
122
+ "dataScope",
123
+ "permissionKeys",
124
+ "menuKeys",
125
+ "departmentIds",
126
+ "status",
127
+ ]),
128
+ }),
129
+ toEntity: (row) =>
130
+ ({
131
+ ...auditEntity(row),
132
+ ...pick(row, ["id", "code", "name", "dataScope", "status"]),
133
+ permissionKeys: array(row.permissionKeys),
134
+ menuKeys: array(row.menuKeys),
135
+ departmentIds: array(row.departmentIds),
136
+ }) as ManagedRole,
137
+ };
138
+ }
139
+
140
+ export function departmentConfig(
141
+ seedItems: ManagedDepartment[],
142
+ ): DrizzleEntityRepositoryConfig<ManagedDepartment> {
143
+ return {
144
+ table: departments,
145
+ idColumn: "id",
146
+ seedItems,
147
+ toRow: (item) => ({
148
+ ...auditRow(item),
149
+ ...pick(item, ["id", "parentId", "name", "code", "order", "status"]),
150
+ }),
151
+ toEntity: (row) =>
152
+ ({
153
+ ...auditEntity(row),
154
+ ...pick(row, ["id", "parentId", "name", "code", "order", "status"]),
155
+ }) as ManagedDepartment,
156
+ };
157
+ }
158
+
159
+ export function menuConfig(
160
+ seedItems: ManagedMenu[],
161
+ ): DrizzleEntityRepositoryConfig<ManagedMenu> {
162
+ return {
163
+ table: menus,
164
+ idColumn: "id",
165
+ seedItems,
166
+ toRow: (item) => ({
167
+ ...auditRow(item),
168
+ ...pick(item, [
169
+ "id",
170
+ "key",
171
+ "title",
172
+ "type",
173
+ "order",
174
+ "path",
175
+ "component",
176
+ "icon",
177
+ "permissionKey",
178
+ "featureKey",
179
+ "parentKey",
180
+ "status",
181
+ ]),
182
+ visible: item.visible ? "visible" : "hidden",
183
+ }),
184
+ toEntity: (row) =>
185
+ ({
186
+ ...auditEntity(row),
187
+ ...pick(row, [
188
+ "id",
189
+ "key",
190
+ "title",
191
+ "type",
192
+ "order",
193
+ "path",
194
+ "component",
195
+ "icon",
196
+ "permissionKey",
197
+ "featureKey",
198
+ "parentKey",
199
+ "status",
200
+ ]),
201
+ visible: row.visible !== "hidden",
202
+ }) as ManagedMenu,
203
+ };
204
+ }
205
+
206
+ export function dictionaryConfig(
207
+ seedItems: ManagedDictionary[],
208
+ ): DrizzleEntityRepositoryConfig<ManagedDictionary> {
209
+ return {
210
+ table: dictionaries,
211
+ idColumn: "id",
212
+ seedItems,
213
+ toRow: (item) => ({
214
+ ...auditRow(item),
215
+ ...pick(item, ["id", "code", "name", "description", "status"]),
216
+ items: item.items || [],
217
+ }),
218
+ toEntity: (row) =>
219
+ ({
220
+ ...auditEntity(row),
221
+ ...pick(row, ["id", "code", "name", "description", "status"]),
222
+ items: array(row.items),
223
+ }) as ManagedDictionary,
224
+ };
225
+ }
226
+
227
+ export function featureConfig(
228
+ seedItems: ManagedFeature[],
229
+ ): DrizzleEntityRepositoryConfig<ManagedFeature> {
230
+ return {
231
+ table: featureFlags,
232
+ idColumn: "key",
233
+ seedItems,
234
+ toRow: (item) => ({
235
+ ...auditRow(item),
236
+ key: item.key || item.id,
237
+ module: item.module,
238
+ label: item.label,
239
+ description: item.description,
240
+ enabled: item.enabled,
241
+ visible: item.visible,
242
+ stage: item.stage,
243
+ licenseRequired: false,
244
+ licenseKey: item.requiredEntitlement,
245
+ allowedLicenseVersions: item.licenseVersionOverride,
246
+ dependencies: item.dependencies || [],
247
+ status: item.status,
248
+ }),
249
+ toEntity: (row) =>
250
+ ({
251
+ ...auditEntity(row),
252
+ id: String(row.key),
253
+ key: String(row.key),
254
+ module: String(row.module),
255
+ label: String(row.label),
256
+ description: row.description as string | undefined,
257
+ enabled: Boolean(row.enabled),
258
+ visible: row.visible === "hidden" ? "hidden" : "visible",
259
+ stage: String(row.stage || "stable") as ManagedFeature["stage"],
260
+ requiredEntitlement: row.licenseKey
261
+ ? (row.licenseKey as NonNullable<
262
+ ManagedFeature["requiredEntitlement"]
263
+ >)
264
+ : undefined,
265
+ licenseVersionOverride: Array.isArray(row.allowedLicenseVersions)
266
+ ? (row.allowedLicenseVersions as NonNullable<
267
+ ManagedFeature["licenseVersionOverride"]
268
+ >)
269
+ : undefined,
270
+ dependencies: array(row.dependencies),
271
+ status: String(row.status || "active") as ManagedFeature["status"],
272
+ }) as ManagedFeature,
273
+ };
274
+ }
275
+
276
+ // @windy-module system.license begin
277
+ export function licenseConfig(
278
+ seedItems: ManagedLicense[],
279
+ ): DrizzleEntityRepositoryConfig<ManagedLicense> {
280
+ return {
281
+ table: legacyLicenses,
282
+ idColumn: "id",
283
+ seedItems,
284
+ toRow: (item) => ({
285
+ ...auditRow(item),
286
+ ...pick(item, [
287
+ "id",
288
+ "licenseKey",
289
+ "subject",
290
+ "machineCode",
291
+ "publicKeyId",
292
+ "edition",
293
+ "modules",
294
+ "status",
295
+ ]),
296
+ issuedAt: date(item.issuedAt),
297
+ expiresAt: item.expiresAt ? date(item.expiresAt) : undefined,
298
+ payload: { modules: item.modules },
299
+ signature: "",
300
+ }),
301
+ toEntity: (row) =>
302
+ ({
303
+ ...auditEntity(row),
304
+ ...pick(row, [
305
+ "id",
306
+ "licenseKey",
307
+ "subject",
308
+ "machineCode",
309
+ "publicKeyId",
310
+ "edition",
311
+ "status",
312
+ ]),
313
+ modules: array(row.modules),
314
+ issuedAt: iso(row.issuedAt),
315
+ expiresAt: optionalIso(row.expiresAt),
316
+ }) as ManagedLicense,
317
+ };
318
+ }
319
+ // @windy-module system.license end