create-windy 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/cli.js +312 -154
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/server/src/application-services.ts +2 -2
- package/template/apps/server/src/audit/search-summary.ts +15 -8
- package/template/apps/server/src/example-modules.ts +58 -70
- package/template/apps/server/src/foundation.ts +8 -4
- package/template/apps/server/src/guards.test.ts +13 -0
- package/template/apps/server/src/guards.ts +12 -2
- package/template/apps/server/src/index.ts +48 -91
- package/template/apps/server/src/installed-business-modules.ts +23 -4
- package/template/apps/server/src/module-bootstrap.ts +83 -0
- package/template/apps/server/src/module-composition.test.ts +45 -1
- package/template/apps/server/src/module-composition.ts +45 -0
- package/template/apps/server/src/module-host/initialize.test.ts +123 -0
- package/template/apps/server/src/module-host/initialize.ts +331 -0
- package/template/apps/server/src/module-host/request-context.test.ts +66 -0
- package/template/apps/server/src/module-host/request-context.ts +156 -0
- package/template/apps/server/src/module-host/role-presets.test.ts +94 -0
- package/template/apps/server/src/module-host/role-presets.ts +76 -0
- package/template/apps/server/src/module-host/route-installer.test.ts +317 -0
- package/template/apps/server/src/module-host/route-installer.ts +97 -0
- package/template/apps/server/src/module-host/route-registration.test.ts +160 -0
- package/template/apps/server/src/module-host/search-providers.ts +31 -0
- package/template/apps/server/src/module-host/storage-port.test.ts +165 -0
- package/template/apps/server/src/module-host/storage-port.ts +59 -0
- package/template/apps/server/src/module-host/task-definitions.ts +41 -0
- package/template/apps/server/src/module-host/test-fixtures.ts +26 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +25 -0
- package/template/apps/server/src/module-host.test.ts +202 -58
- package/template/apps/server/src/module-host.ts +34 -113
- package/template/apps/server/src/route-guards.test.ts +112 -1
- package/template/apps/server/src/runtime-feature.ts +66 -31
- package/template/apps/server/src/runtime.test.ts +1 -0
- package/template/apps/server/src/runtime.ts +3 -1
- package/template/apps/server/src/system/built-in-roles.ts +1 -1
- package/template/apps/server/src/work-order/foundation-modules.test.ts +30 -10
- package/template/apps/server/src/work-order/routes.test.ts +30 -18
- package/template/apps/server/src/work-order/routes.ts +104 -135
- package/template/apps/server/src/work-order/search-api.test.ts +14 -3
- package/template/apps/server/src/work-order/search-provider.test.ts +23 -15
- package/template/apps/server/src/work-order/search-provider.ts +17 -12
- package/template/apps/server/src/work-order/task.test.ts +16 -11
- package/template/apps/web/Dockerfile +4 -1
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +4 -1
- package/template/apps/web/src/layout/GlobalWatermark.vue +2 -0
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +4 -1
- package/template/docs/architecture/object-storage.md +12 -0
- package/template/packages/config/index.test.ts +57 -0
- package/template/packages/config/package.json +1 -1
- package/template/packages/config/src/platform.ts +28 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/runner.ts +7 -9
- package/template/packages/example-work-order/index.ts +1 -0
- package/template/packages/example-work-order/package.json +2 -0
- package/template/packages/example-work-order/src/server-module.ts +61 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs/src/types.ts +7 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/catalog-validation.test.ts +212 -0
- package/template/packages/modules/src/catalog-validation.ts +19 -2
- package/template/packages/modules/src/migration-bundle-validation.test.ts +191 -0
- package/template/packages/server-sdk/index.ts +42 -0
- package/template/packages/server-sdk/package.json +16 -3
- package/template/packages/server-sdk/src/actor.ts +15 -0
- package/template/packages/server-sdk/src/audit-port.ts +22 -0
- package/template/packages/server-sdk/src/jobs-port.ts +24 -0
- package/template/packages/server-sdk/src/module-host.ts +36 -1
- package/template/packages/server-sdk/src/namespace.test.ts +26 -0
- package/template/packages/server-sdk/src/namespace.ts +13 -0
- package/template/packages/server-sdk/src/request-context.ts +21 -0
- package/template/packages/server-sdk/src/role-preset.ts +18 -0
- package/template/packages/server-sdk/src/route-definition.ts +26 -0
- package/template/packages/server-sdk/src/search-provider-port.ts +48 -0
- package/template/packages/server-sdk/src/storage-port.ts +25 -0
- package/template/packages/server-sdk/src/task-registration.ts +18 -0
- package/template/packages/server-sdk/src/tool-registration.ts +29 -0
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/license-catalog.test.ts +73 -1
- package/template/packages/shared/src/license-catalog.ts +95 -0
- package/template/packages/{database/src/bundle-validation.ts → shared/src/migration-bundle-validation.ts} +2 -1
- package/template/apps/server/src/work-order/task.ts +0 -30
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@ interface ApplicationServicesInput {
|
|
|
40
40
|
foundation: ReturnType<typeof createFoundationSnapshot>;
|
|
41
41
|
runtime: ReturnType<typeof createServerRuntime>;
|
|
42
42
|
storage: ReturnType<typeof createStorageRuntime>;
|
|
43
|
-
|
|
43
|
+
moduleTasks: ScheduledTaskDefinition[];
|
|
44
44
|
bulkRetentionDays: number;
|
|
45
45
|
installModuleDurableHandlers?: (registry: DurableHandlerRegistry) => void;
|
|
46
46
|
}
|
|
@@ -90,7 +90,7 @@ export async function createApplicationServices(
|
|
|
90
90
|
return { claimed: Boolean(result && result.status !== "idle") };
|
|
91
91
|
},
|
|
92
92
|
}),
|
|
93
|
-
...input.
|
|
93
|
+
...input.moduleTasks,
|
|
94
94
|
];
|
|
95
95
|
const services = createLegacyServiceRuntime({
|
|
96
96
|
db: persistence?.db,
|
|
@@ -2,13 +2,26 @@ import type { AuditEvent } from "@southwind-ai/shared";
|
|
|
2
2
|
import { randomUUIDv7 } from "bun";
|
|
3
3
|
import type { RequestGuardContext } from "../guards.js";
|
|
4
4
|
|
|
5
|
+
export function searchAuditMetadata(
|
|
6
|
+
query: string,
|
|
7
|
+
matched: number,
|
|
8
|
+
): Record<string, unknown> {
|
|
9
|
+
const normalized = query.trim();
|
|
10
|
+
return {
|
|
11
|
+
queryDigest: new Bun.CryptoHasher("sha256")
|
|
12
|
+
.update(normalized)
|
|
13
|
+
.digest("hex"),
|
|
14
|
+
queryLength: normalized.length,
|
|
15
|
+
matched,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
export function searchAuditEvent(
|
|
6
20
|
context: RequestGuardContext,
|
|
7
21
|
resourceName: string,
|
|
8
22
|
query: string,
|
|
9
23
|
matched: number,
|
|
10
24
|
): AuditEvent {
|
|
11
|
-
const normalized = query.trim();
|
|
12
25
|
return {
|
|
13
26
|
id: randomUUIDv7(),
|
|
14
27
|
action: "data.search",
|
|
@@ -20,13 +33,7 @@ export function searchAuditEvent(
|
|
|
20
33
|
},
|
|
21
34
|
target: { type: resourceName },
|
|
22
35
|
requestId: context.requestId,
|
|
23
|
-
metadata:
|
|
24
|
-
queryDigest: new Bun.CryptoHasher("sha256")
|
|
25
|
-
.update(normalized)
|
|
26
|
-
.digest("hex"),
|
|
27
|
-
queryLength: normalized.length,
|
|
28
|
-
matched,
|
|
29
|
-
},
|
|
36
|
+
metadata: searchAuditMetadata(query, matched),
|
|
30
37
|
occurredAt: new Date().toISOString(),
|
|
31
38
|
};
|
|
32
39
|
}
|
|
@@ -1,86 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
// @windy-module work-order begin
|
|
2
|
+
import {
|
|
3
|
+
createWorkOrderServerModule,
|
|
4
|
+
type WorkOrder,
|
|
5
|
+
} from "@southwind-ai/example-work-order";
|
|
6
|
+
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
7
|
+
import type {
|
|
8
|
+
InstallableServerModule,
|
|
9
|
+
ModuleInitializer,
|
|
10
|
+
} from "@southwind-ai/server-sdk";
|
|
2
11
|
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import type { SearchProvider } from "./search/contracts.js";
|
|
12
|
+
import { platformDataActor } from "./data-access/platform-actor.js";
|
|
13
|
+
import type { ScopedEntityRepository } from "./data-access/scoped-repository.js";
|
|
6
14
|
import type { ManagedDepartment } from "./system/entities.js";
|
|
7
15
|
import type { EntityRepository } from "./system/repository.js";
|
|
8
|
-
import type { RouteApp } from "./system/route-types.js";
|
|
9
|
-
// @windy-module work-order begin
|
|
10
|
-
import type { ModuleInitializer } from "@southwind-ai/server-sdk";
|
|
11
16
|
import { createWorkOrderRepository } from "./work-order/repository.js";
|
|
12
17
|
import { createDrizzleWorkOrderRepository } from "./work-order/drizzle-repository.js";
|
|
13
|
-
import {
|
|
14
|
-
import { WorkOrderService } from "./work-order/service.js";
|
|
18
|
+
import { createWorkOrderRouteDefinitions } from "./work-order/routes.js";
|
|
15
19
|
import { WorkOrderSearchProvider } from "./work-order/search-provider.js";
|
|
16
|
-
import {
|
|
17
|
-
// @windy-module work-order end
|
|
18
|
-
|
|
19
|
-
// @windy-module work-order begin
|
|
20
|
-
export const workOrderModuleInitializer: ModuleInitializer = {
|
|
21
|
-
moduleName: "work-order",
|
|
22
|
-
initialize(host) {
|
|
23
|
-
host.registerUploadPolicy({
|
|
24
|
-
purpose: "work-order.ticket.attachment",
|
|
25
|
-
maxByteSize: 20 * 1024 * 1024,
|
|
26
|
-
maxPartByteSize: 4 * 1024 * 1024,
|
|
27
|
-
sessionTtlMs: 2 * 60 * 60_000,
|
|
28
|
-
allowedTypes: {
|
|
29
|
-
"application/pdf": ["pdf"],
|
|
30
|
-
"image/png": ["png"],
|
|
31
|
-
"image/jpeg": ["jpg", "jpeg"],
|
|
32
|
-
"text/plain": ["txt"],
|
|
33
|
-
},
|
|
34
|
-
signature: "none",
|
|
35
|
-
});
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
// @windy-module work-order end
|
|
20
|
+
import { WorkOrderService } from "./work-order/service.js";
|
|
39
21
|
|
|
40
|
-
export interface
|
|
41
|
-
features: FeatureFlagDefinition[];
|
|
22
|
+
export interface WorkOrderModuleDependencies {
|
|
42
23
|
departments: EntityRepository<ManagedDepartment>;
|
|
43
24
|
db?: NodePgDatabase;
|
|
25
|
+
/** 测试可注入预置数据的仓储;默认按 db 选择 Drizzle 或内存实现。 */
|
|
26
|
+
repository?: ScopedEntityRepository<WorkOrder>;
|
|
44
27
|
}
|
|
45
28
|
|
|
46
|
-
export interface
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
export interface WorkOrderModule {
|
|
30
|
+
readonly manifest: ModuleManifest;
|
|
31
|
+
setup: InstallableServerModule<ModuleManifest>["setup"];
|
|
32
|
+
/** @deprecated 测试兼容入口;生产装配使用 manifest + setup。 */
|
|
33
|
+
readonly initializer: ModuleInitializer;
|
|
34
|
+
/** 宿主运行时就绪后挂载模块服务;路由与任务 Handler 延迟解析它。 */
|
|
35
|
+
attach(dependencies: WorkOrderModuleDependencies): void;
|
|
50
36
|
}
|
|
51
37
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
38
|
+
/**
|
|
39
|
+
* 示例工单模块:以 initializer + Host 声明式装配的样板。
|
|
40
|
+
* Manifest 声明 Route/Task/Provider,初始化器注册实现与受限 Port 定义,
|
|
41
|
+
* 不再由宿主手工逐项接线。
|
|
42
|
+
*/
|
|
43
|
+
export function createWorkOrderModule(): WorkOrderModule {
|
|
44
|
+
let service: WorkOrderService | undefined;
|
|
45
|
+
const requireService = () => {
|
|
46
|
+
if (!service) throw new Error("示例工单模块尚未挂载");
|
|
47
|
+
return service;
|
|
48
|
+
};
|
|
49
|
+
const serverModule = createWorkOrderServerModule({
|
|
50
|
+
routes: createWorkOrderRouteDefinitions(requireService),
|
|
51
|
+
snapshot: () =>
|
|
52
|
+
requireService().operationalSnapshot(
|
|
53
|
+
platformDataActor("task_work_order_snapshot", "工单运营快照任务"),
|
|
54
|
+
),
|
|
55
|
+
searchProvider: new WorkOrderSearchProvider(requireService),
|
|
56
|
+
});
|
|
64
57
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
...serverModule,
|
|
59
|
+
initializer: {
|
|
60
|
+
moduleName: serverModule.manifest.name,
|
|
61
|
+
initialize: serverModule.setup,
|
|
62
|
+
},
|
|
63
|
+
attach(dependencies) {
|
|
64
|
+
service = new WorkOrderService(
|
|
65
|
+
dependencies.repository ??
|
|
66
|
+
(dependencies.db
|
|
67
|
+
? createDrizzleWorkOrderRepository(dependencies.db)
|
|
68
|
+
: createWorkOrderRepository()),
|
|
69
|
+
dependencies.departments,
|
|
70
|
+
);
|
|
71
|
+
},
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
|
-
|
|
71
|
-
export interface ExampleModuleRouteDependencies {
|
|
72
|
-
app: RouteApp;
|
|
73
|
-
runtime: ReturnType<typeof createServerRuntime>;
|
|
74
|
-
moduleRuntime?: ExampleModuleRuntime;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function registerExampleModuleRoutes(
|
|
78
|
-
dependencies: ExampleModuleRouteDependencies,
|
|
79
|
-
): void {
|
|
80
|
-
if (!dependencies.moduleRuntime) return;
|
|
81
|
-
registerWorkOrderRoutes(
|
|
82
|
-
dependencies.app,
|
|
83
|
-
dependencies.moduleRuntime.service,
|
|
84
|
-
dependencies.runtime,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
74
|
+
// @windy-module work-order end
|
|
@@ -13,7 +13,8 @@ import type {
|
|
|
13
13
|
MenuNode,
|
|
14
14
|
PermissionDefinition,
|
|
15
15
|
} from "@southwind-ai/shared";
|
|
16
|
-
import {
|
|
16
|
+
import { resolveLicenseCatalog } from "@southwind-ai/shared";
|
|
17
|
+
import { installedBusinessModules } from "./installed-business-modules.js";
|
|
17
18
|
|
|
18
19
|
export interface FoundationSnapshot {
|
|
19
20
|
platform: {
|
|
@@ -38,8 +39,8 @@ export interface FoundationSnapshot {
|
|
|
38
39
|
export function loadFoundationModules(): ModuleManifest[] {
|
|
39
40
|
const registry = createModuleRegistry();
|
|
40
41
|
for (const factory of systemModuleFactories) registry.register(factory);
|
|
41
|
-
for (const
|
|
42
|
-
registry.register(
|
|
42
|
+
for (const module of installedBusinessModules) {
|
|
43
|
+
registry.register(() => module.manifest);
|
|
43
44
|
}
|
|
44
45
|
return registry.list();
|
|
45
46
|
}
|
|
@@ -72,7 +73,10 @@ export function createFoundationSnapshot(
|
|
|
72
73
|
config: PlatformConfig = loadPlatformConfig(),
|
|
73
74
|
): FoundationSnapshot {
|
|
74
75
|
const modules = loadFoundationModules();
|
|
75
|
-
const catalogs = validateCapabilityCatalogs(
|
|
76
|
+
const catalogs = validateCapabilityCatalogs(
|
|
77
|
+
modules,
|
|
78
|
+
resolveLicenseCatalog(config.license.versions),
|
|
79
|
+
);
|
|
76
80
|
|
|
77
81
|
return {
|
|
78
82
|
platform: {
|
|
@@ -71,4 +71,17 @@ describe("guards", () => {
|
|
|
71
71
|
}).reason,
|
|
72
72
|
).toBe("entitlement-not-granted");
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
test("运行时解析出依赖缺失时拒绝并记录 security.denied", () => {
|
|
76
|
+
const denied = evaluateFeatureGuard(context, {
|
|
77
|
+
...feature,
|
|
78
|
+
enabled: false,
|
|
79
|
+
disabledReason: "dependency-missing",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(denied.allowed).toBe(false);
|
|
83
|
+
expect(denied.reason).toBe("dependency-missing");
|
|
84
|
+
expect(denied.auditEvent?.action).toBe("security.denied");
|
|
85
|
+
expect(denied.auditEvent?.target?.id).toBe(feature.key);
|
|
86
|
+
});
|
|
74
87
|
});
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
AuditEvent,
|
|
4
4
|
EntitlementKey,
|
|
5
5
|
FeatureFlagDefinition,
|
|
6
|
+
FeatureFlagState,
|
|
6
7
|
LicenseVersionKey,
|
|
7
8
|
} from "@southwind-ai/shared";
|
|
8
9
|
import { correlationId, isLicenseVersionAllowed } from "@southwind-ai/shared";
|
|
@@ -47,6 +48,7 @@ export type GuardDecisionReason =
|
|
|
47
48
|
| "missing-permission"
|
|
48
49
|
| "role-not-allowed"
|
|
49
50
|
| "feature-disabled"
|
|
51
|
+
| "dependency-missing"
|
|
50
52
|
| "license-version-not-allowed"
|
|
51
53
|
| "entitlement-not-granted"
|
|
52
54
|
| "license-restricted";
|
|
@@ -120,13 +122,21 @@ export function evaluateRoleGuard(
|
|
|
120
122
|
|
|
121
123
|
export function evaluateFeatureGuard(
|
|
122
124
|
context: RequestGuardContext,
|
|
123
|
-
feature: FeatureFlagDefinition
|
|
125
|
+
feature: FeatureFlagDefinition & {
|
|
126
|
+
disabledReason?: FeatureFlagState["reason"];
|
|
127
|
+
},
|
|
124
128
|
): GuardDecision {
|
|
125
129
|
if (feature.enabled && feature.visible === "visible") {
|
|
126
130
|
return { allowed: true, reason: "allowed" };
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
return deniedDecision(
|
|
133
|
+
return deniedDecision(
|
|
134
|
+
context,
|
|
135
|
+
feature.disabledReason === "dependency-missing"
|
|
136
|
+
? "dependency-missing"
|
|
137
|
+
: "feature-disabled",
|
|
138
|
+
feature.key,
|
|
139
|
+
);
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
export function evaluateEntitlementGuard(
|
|
@@ -2,10 +2,6 @@ import { loadPlatformConfig } from "@southwind-ai/config";
|
|
|
2
2
|
import pino from "pino";
|
|
3
3
|
import { registerAdminBootstrapRoute } from "./admin/routes.js";
|
|
4
4
|
import { registerSessionContextRoute } from "./session/routes.js";
|
|
5
|
-
import {
|
|
6
|
-
createFoundationSnapshot,
|
|
7
|
-
loadFoundationModules,
|
|
8
|
-
} from "./foundation.js";
|
|
9
5
|
import { registerFoundationRoute } from "./foundation-route.js";
|
|
10
6
|
// @windy-module system.scheduler begin
|
|
11
7
|
import { createApplicationServices } from "./application-services.js";
|
|
@@ -44,10 +40,6 @@ import { registerSystemOperationRoutes } from "./system/operations.js";
|
|
|
44
40
|
import { createPostgresMonitorProbe } from "./system/postgres-monitoring.js";
|
|
45
41
|
// @windy-module system.operations end
|
|
46
42
|
import { registerSystemRoutes } from "./system/routes.js";
|
|
47
|
-
import {
|
|
48
|
-
createSystemRepositories,
|
|
49
|
-
synchronizeBuiltInRoles,
|
|
50
|
-
} from "./system/seed.js";
|
|
51
43
|
// @windy-module system.scheduler begin
|
|
52
44
|
import { registerSchedulerRoutes } from "./scheduler/routes.js";
|
|
53
45
|
import { SchedulerWorker } from "./scheduler/worker.js";
|
|
@@ -59,25 +51,17 @@ import { registerProfileRoutes } from "./profile/routes.js";
|
|
|
59
51
|
// @windy-module system.notification begin
|
|
60
52
|
import { registerNotificationRoutes } from "./notification/routes.js";
|
|
61
53
|
// @windy-module system.notification end
|
|
62
|
-
// @windy-module work-order begin
|
|
63
|
-
import {
|
|
64
|
-
createExampleModuleRuntime,
|
|
65
|
-
registerExampleModuleRoutes,
|
|
66
|
-
workOrderModuleInitializer,
|
|
67
|
-
} from "./example-modules.js";
|
|
68
|
-
// @windy-module work-order end
|
|
69
|
-
// @windy-module system.configuration begin
|
|
70
|
-
import { bootstrapModuleConfiguration } from "./configuration/bootstrap.js";
|
|
71
|
-
// @windy-module system.configuration end
|
|
72
54
|
// @windy-module system.storage begin
|
|
73
|
-
import {
|
|
74
|
-
createStorageRuntime,
|
|
75
|
-
platformUploadPolicies,
|
|
76
|
-
} from "./storage/runtime.js";
|
|
55
|
+
import { createStorageRuntime } from "./storage/runtime.js";
|
|
77
56
|
import { registerStorageRoutes } from "./storage/routes.js";
|
|
78
57
|
// @windy-module system.storage end
|
|
79
58
|
import { validateServerModuleComposition } from "./module-composition.js";
|
|
80
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
adaptModuleSearchProviders,
|
|
61
|
+
createModuleTaskDefinitions,
|
|
62
|
+
createModuleToolHandlerMap,
|
|
63
|
+
installModuleRoutes,
|
|
64
|
+
} from "./module-host.js";
|
|
81
65
|
// @windy-module system.search begin
|
|
82
66
|
import { createSearchHost } from "./search/host.js";
|
|
83
67
|
// @windy-module system.search end
|
|
@@ -88,6 +72,7 @@ import {
|
|
|
88
72
|
createPlatformObservability,
|
|
89
73
|
logPlatformStartup,
|
|
90
74
|
} from "./observability/bootstrap.js";
|
|
75
|
+
import { bootstrapServerModules } from "./module-bootstrap.js";
|
|
91
76
|
|
|
92
77
|
const logger = pino({ name: "windy-server" });
|
|
93
78
|
const platformConfig = loadPlatformConfig();
|
|
@@ -101,45 +86,16 @@ const persistence = createServerPersistence(
|
|
|
101
86
|
// @windy-module system.audit begin
|
|
102
87
|
await persistence?.auditWriter.recover();
|
|
103
88
|
// @windy-module system.audit end
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const foundationModules = loadFoundationModules();
|
|
115
|
-
const moduleHosts = await initializeModuleHosts({
|
|
116
|
-
modules: foundationModules,
|
|
117
|
-
initializers: [
|
|
118
|
-
// @windy-module work-order begin
|
|
119
|
-
workOrderModuleInitializer,
|
|
120
|
-
// @windy-module work-order end
|
|
121
|
-
],
|
|
122
|
-
baseUploadPolicies: [
|
|
123
|
-
// @windy-module system.storage begin
|
|
124
|
-
...platformUploadPolicies,
|
|
125
|
-
// @windy-module system.storage end
|
|
126
|
-
],
|
|
127
|
-
});
|
|
128
|
-
void moduleHosts;
|
|
129
|
-
const systemRepositories = createSystemRepositories(foundationSnapshot, {
|
|
130
|
-
db: persistence?.db,
|
|
131
|
-
});
|
|
132
|
-
const featureSync = await systemRepositories.features.syncManifest(
|
|
133
|
-
foundationSnapshot.features,
|
|
134
|
-
);
|
|
135
|
-
await systemRepositories.menuRegistry.synchronize({
|
|
136
|
-
id: "system_bootstrap",
|
|
137
|
-
type: "service",
|
|
138
|
-
name: "平台初始化",
|
|
139
|
-
roleCodes: ["system"],
|
|
140
|
-
permissionKeys: ["system.menu.manage"],
|
|
141
|
-
});
|
|
142
|
-
await synchronizeBuiltInRoles(systemRepositories, foundationSnapshot);
|
|
89
|
+
const {
|
|
90
|
+
// @windy-module system.configuration begin
|
|
91
|
+
configurationRuntime,
|
|
92
|
+
// @windy-module system.configuration end
|
|
93
|
+
featureSync,
|
|
94
|
+
foundationModules,
|
|
95
|
+
foundationSnapshot,
|
|
96
|
+
moduleHosts,
|
|
97
|
+
systemRepositories,
|
|
98
|
+
} = await bootstrapServerModules(platformConfig, persistence);
|
|
143
99
|
// @windy-module system.license begin
|
|
144
100
|
const platformLicenseOptions = createPlatformLicenseRuntimeOptions(process.env);
|
|
145
101
|
const platformLicenseRuntime = new PlatformLicenseRuntime(
|
|
@@ -195,33 +151,18 @@ const platformSettings = createPlatformSettingsRuntime(persistence?.db, {
|
|
|
195
151
|
logoUrl: platformConfig.logoUrl,
|
|
196
152
|
});
|
|
197
153
|
// @windy-module system.settings end
|
|
198
|
-
// @windy-module work-order begin
|
|
199
|
-
const exampleModuleRuntime = createExampleModuleRuntime({
|
|
200
|
-
features: foundationSnapshot.features,
|
|
201
|
-
departments: systemRepositories.departments,
|
|
202
|
-
db: persistence?.db,
|
|
203
|
-
});
|
|
204
|
-
// @windy-module work-order end
|
|
205
|
-
// @windy-module system.search begin
|
|
206
|
-
const businessSearchProviders = [
|
|
207
|
-
// @windy-module work-order begin
|
|
208
|
-
...(exampleModuleRuntime?.providers || []),
|
|
209
|
-
// @windy-module work-order end
|
|
210
|
-
];
|
|
211
|
-
// @windy-module system.search end
|
|
212
154
|
// @windy-module system.search begin
|
|
213
155
|
const searchHost = createSearchHost(
|
|
214
156
|
foundationModules,
|
|
215
|
-
{ providers:
|
|
157
|
+
{ providers: adaptModuleSearchProviders(moduleHosts) },
|
|
216
158
|
runtime,
|
|
217
159
|
);
|
|
218
160
|
// @windy-module system.search end
|
|
219
161
|
// @windy-module system.scheduler begin
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
];
|
|
162
|
+
const moduleTaskDefinitions = createModuleTaskDefinitions(
|
|
163
|
+
moduleHosts,
|
|
164
|
+
foundationSnapshot.features,
|
|
165
|
+
);
|
|
225
166
|
const {
|
|
226
167
|
bulkDataService,
|
|
227
168
|
scheduler,
|
|
@@ -229,13 +170,14 @@ const {
|
|
|
229
170
|
notifications: notificationRepository,
|
|
230
171
|
workflow: workflowService,
|
|
231
172
|
governedExportService,
|
|
173
|
+
durable: durableJobRuntime,
|
|
232
174
|
} = await createApplicationServices({
|
|
233
175
|
persistence,
|
|
234
176
|
systemRepositories,
|
|
235
177
|
foundation: foundationSnapshot,
|
|
236
178
|
runtime,
|
|
237
179
|
storage: storageRuntime,
|
|
238
|
-
|
|
180
|
+
moduleTasks: moduleTaskDefinitions,
|
|
239
181
|
bulkRetentionDays: readPositiveInt(process.env.BULK_DATA_RETENTION_DAYS, 7),
|
|
240
182
|
installModuleDurableHandlers: moduleHosts.installDurableHandlers,
|
|
241
183
|
});
|
|
@@ -246,19 +188,15 @@ const schedulerWorker = new SchedulerWorker(
|
|
|
246
188
|
(result) => observability.metrics.observeSchedulerTick(result),
|
|
247
189
|
);
|
|
248
190
|
// @windy-module system.scheduler end
|
|
249
|
-
|
|
250
191
|
await bootstrapAdminPassword({
|
|
251
192
|
authService,
|
|
252
193
|
authRepository: persistence?.authRepository,
|
|
253
194
|
repositories: systemRepositories,
|
|
254
195
|
password: process.env.WINDY_BOOTSTRAP_ADMIN_PASSWORD,
|
|
255
196
|
});
|
|
256
|
-
|
|
257
197
|
const app = createPlatformHttpApp(runtime, observability);
|
|
258
198
|
observability.register(app);
|
|
259
|
-
|
|
260
199
|
registerAuthSessionRoute(app);
|
|
261
|
-
|
|
262
200
|
registerAuthRoutes(app, authService, {
|
|
263
201
|
sessionCookieName: platformConfig.security.sessionCookieName,
|
|
264
202
|
secureCookie: process.env.NODE_ENV === "production",
|
|
@@ -345,7 +283,10 @@ registerSystemOperationRoutes(app, runtime, foundationSnapshot.features, {
|
|
|
345
283
|
});
|
|
346
284
|
// @windy-module system.operations end
|
|
347
285
|
// @windy-module system.agent begin
|
|
348
|
-
const toolHandlers =
|
|
286
|
+
const toolHandlers = new Map([
|
|
287
|
+
...createSystemToolHandlers(systemRepositories, runtime),
|
|
288
|
+
...createModuleToolHandlerMap(moduleHosts),
|
|
289
|
+
]);
|
|
349
290
|
registerAgentRoutes(app, foundationModules, toolHandlers, runtime);
|
|
350
291
|
// @windy-module system.agent end
|
|
351
292
|
// @windy-module system.scheduler begin
|
|
@@ -360,10 +301,14 @@ registerBulkDataRoutes(
|
|
|
360
301
|
);
|
|
361
302
|
// @windy-module system.bulk-data end
|
|
362
303
|
// @windy-module work-order begin
|
|
363
|
-
|
|
364
|
-
app,
|
|
304
|
+
installModuleRoutes(moduleHosts, app, {
|
|
365
305
|
runtime,
|
|
366
|
-
|
|
306
|
+
features: foundationSnapshot.features,
|
|
307
|
+
jobs: durableJobRuntime.service,
|
|
308
|
+
storage: {
|
|
309
|
+
uploads: storageRuntime.uploads,
|
|
310
|
+
files: storageRuntime.files,
|
|
311
|
+
},
|
|
367
312
|
});
|
|
368
313
|
// @windy-module work-order end
|
|
369
314
|
|
|
@@ -379,6 +324,18 @@ validateServerModuleComposition({
|
|
|
379
324
|
// @windy-module system.search begin
|
|
380
325
|
providers: searchHost.providerKeys,
|
|
381
326
|
// @windy-module system.search end
|
|
327
|
+
moduleRegistrations: {
|
|
328
|
+
routes: moduleHosts.routes.map(
|
|
329
|
+
({ definition }) => `${definition.method} ${definition.path}`,
|
|
330
|
+
),
|
|
331
|
+
tasks: moduleHosts.taskHandlers.map(
|
|
332
|
+
({ registration }) => registration.taskKey,
|
|
333
|
+
),
|
|
334
|
+
tools: moduleHosts.toolHandlers.map(
|
|
335
|
+
({ registration }) => registration.toolKey,
|
|
336
|
+
),
|
|
337
|
+
providers: moduleHosts.searchProviders.map(({ provider }) => provider.key),
|
|
338
|
+
},
|
|
382
339
|
});
|
|
383
340
|
|
|
384
341
|
const port = process.env.PORT ? Number(process.env.PORT) : 9_747;
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
// @windy-module work-order begin
|
|
2
|
-
import {
|
|
2
|
+
import { createWorkOrderModule } from "./example-modules.js";
|
|
3
3
|
// @windy-module work-order end
|
|
4
|
-
import type {
|
|
4
|
+
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
5
|
+
import type { InstallableServerModule } from "@southwind-ai/server-sdk";
|
|
6
|
+
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
7
|
+
import type { ManagedDepartment } from "./system/entities.js";
|
|
8
|
+
import type { EntityRepository } from "./system/repository.js";
|
|
5
9
|
|
|
6
|
-
export
|
|
10
|
+
export interface BusinessModuleRuntime {
|
|
11
|
+
readonly db?: NodePgDatabase;
|
|
12
|
+
readonly departments: EntityRepository<ManagedDepartment>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface InstalledBusinessModule extends InstallableServerModule<ModuleManifest> {
|
|
16
|
+
attach(runtime: BusinessModuleRuntime): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const installedBusinessModules: readonly InstalledBusinessModule[] = [
|
|
7
20
|
// @windy-module work-order begin
|
|
8
|
-
|
|
21
|
+
createWorkOrderModule(),
|
|
9
22
|
// @windy-module work-order end
|
|
10
23
|
];
|
|
24
|
+
|
|
25
|
+
export function attachInstalledBusinessModules(
|
|
26
|
+
runtime: BusinessModuleRuntime,
|
|
27
|
+
): void {
|
|
28
|
+
for (const module of installedBusinessModules) module.attach(runtime);
|
|
29
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { PlatformConfig } from "@southwind-ai/config";
|
|
2
|
+
// @windy-module system.configuration begin
|
|
3
|
+
import { bootstrapModuleConfiguration } from "./configuration/bootstrap.js";
|
|
4
|
+
// @windy-module system.configuration end
|
|
5
|
+
import {
|
|
6
|
+
createFoundationSnapshot,
|
|
7
|
+
loadFoundationModules,
|
|
8
|
+
} from "./foundation.js";
|
|
9
|
+
import {
|
|
10
|
+
attachInstalledBusinessModules,
|
|
11
|
+
installedBusinessModules,
|
|
12
|
+
} from "./installed-business-modules.js";
|
|
13
|
+
import {
|
|
14
|
+
initializeModuleHosts,
|
|
15
|
+
synchronizeModuleRolePresets,
|
|
16
|
+
} from "./module-host.js";
|
|
17
|
+
import type { ServerPersistence } from "./persistence.js";
|
|
18
|
+
// @windy-module system.storage begin
|
|
19
|
+
import { platformUploadPolicies } from "./storage/runtime.js";
|
|
20
|
+
// @windy-module system.storage end
|
|
21
|
+
import {
|
|
22
|
+
createSystemRepositories,
|
|
23
|
+
synchronizeBuiltInRoles,
|
|
24
|
+
} from "./system/seed.js";
|
|
25
|
+
|
|
26
|
+
export async function bootstrapServerModules(
|
|
27
|
+
platformConfig: PlatformConfig,
|
|
28
|
+
persistence: ServerPersistence | undefined,
|
|
29
|
+
) {
|
|
30
|
+
// @windy-module system.configuration begin
|
|
31
|
+
const configurationRuntime = await bootstrapModuleConfiguration(
|
|
32
|
+
platformConfig,
|
|
33
|
+
persistence?.configVersionRepository,
|
|
34
|
+
);
|
|
35
|
+
// @windy-module system.configuration end
|
|
36
|
+
const foundationSnapshot = createFoundationSnapshot(platformConfig);
|
|
37
|
+
// @windy-module system.configuration begin
|
|
38
|
+
configurationRuntime.bindFoundation(foundationSnapshot);
|
|
39
|
+
// @windy-module system.configuration end
|
|
40
|
+
const foundationModules = loadFoundationModules();
|
|
41
|
+
const moduleHosts = await initializeModuleHosts({
|
|
42
|
+
modules: foundationModules,
|
|
43
|
+
installations: installedBusinessModules,
|
|
44
|
+
baseUploadPolicies: [
|
|
45
|
+
// @windy-module system.storage begin
|
|
46
|
+
...platformUploadPolicies,
|
|
47
|
+
// @windy-module system.storage end
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
const systemRepositories = createSystemRepositories(foundationSnapshot, {
|
|
51
|
+
db: persistence?.db,
|
|
52
|
+
});
|
|
53
|
+
const featureSync = await systemRepositories.features.syncManifest(
|
|
54
|
+
foundationSnapshot.features,
|
|
55
|
+
);
|
|
56
|
+
await systemRepositories.menuRegistry.synchronize({
|
|
57
|
+
id: "system_bootstrap",
|
|
58
|
+
type: "service",
|
|
59
|
+
name: "平台初始化",
|
|
60
|
+
roleCodes: ["system"],
|
|
61
|
+
permissionKeys: ["system.menu.manage"],
|
|
62
|
+
});
|
|
63
|
+
await synchronizeBuiltInRoles(systemRepositories, foundationSnapshot);
|
|
64
|
+
await synchronizeModuleRolePresets(
|
|
65
|
+
systemRepositories,
|
|
66
|
+
foundationSnapshot,
|
|
67
|
+
moduleHosts.rolePresets,
|
|
68
|
+
);
|
|
69
|
+
attachInstalledBusinessModules({
|
|
70
|
+
db: persistence?.db,
|
|
71
|
+
departments: systemRepositories.departments,
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
// @windy-module system.configuration begin
|
|
75
|
+
configurationRuntime,
|
|
76
|
+
// @windy-module system.configuration end
|
|
77
|
+
featureSync,
|
|
78
|
+
foundationModules,
|
|
79
|
+
foundationSnapshot,
|
|
80
|
+
moduleHosts,
|
|
81
|
+
systemRepositories,
|
|
82
|
+
};
|
|
83
|
+
}
|