create-windy 0.3.19 → 0.4.1
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 +28 -13
- package/dist/cli.js +788 -280
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +18 -8
- package/template/README.md +32 -22
- package/template/apps/server/Dockerfile +1 -0
- package/template/apps/server/package.json +1 -0
- package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
- package/template/apps/server/scripts/validate-module-composition.ts +28 -0
- package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
- package/template/apps/server/src/agent/routes.test.ts +20 -7
- package/template/apps/server/src/agent/routes.ts +14 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
- package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
- package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
- package/template/apps/server/src/app/server-composition.ts +56 -0
- package/template/apps/server/src/application-services.ts +2 -0
- package/template/apps/server/src/bulk-data/service.ts +14 -0
- package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
- package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
- package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
- package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
- package/template/apps/server/src/feature/routes.ts +33 -15
- package/template/apps/server/src/foundation-activation.test.ts +60 -0
- package/template/apps/server/src/foundation.ts +64 -5
- package/template/apps/server/src/guards.test.ts +23 -0
- package/template/apps/server/src/guards.ts +12 -0
- package/template/apps/server/src/index.ts +28 -34
- package/template/apps/server/src/installed-business-modules.ts +13 -24
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
- package/template/apps/server/src/license/activation.test.ts +30 -0
- package/template/apps/server/src/license/activation.ts +26 -0
- package/template/apps/server/src/license/restriction.test.ts +16 -0
- package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
- package/template/apps/server/src/license/routes.ts +20 -4
- package/template/apps/server/src/license/runtime-service.test.ts +10 -2
- package/template/apps/server/src/module-bootstrap.ts +14 -23
- package/template/apps/server/src/module-composition.test.ts +43 -2
- package/template/apps/server/src/module-composition.ts +29 -0
- package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
- package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
- package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
- package/template/apps/server/src/module-host/durable-context.ts +42 -2
- package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
- package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
- package/template/apps/server/src/module-host/initialize.test.ts +78 -2
- package/template/apps/server/src/module-host/initialize.ts +15 -4
- package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
- package/template/apps/server/src/module-host/project-attachments.ts +58 -0
- package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
- package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
- package/template/apps/server/src/module-host/project-composition.ts +311 -0
- package/template/apps/server/src/module-host/request-context.ts +3 -0
- package/template/apps/server/src/module-host/route-declaration.ts +15 -6
- package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
- package/template/apps/server/src/module-host/route-installer.ts +12 -2
- package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
- package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
- package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
- package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
- package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
- package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
- package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
- package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
- package/template/apps/server/src/module-host/task-definitions.ts +1 -0
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
- package/template/apps/server/src/module-host.test.ts +2 -0
- package/template/apps/server/src/module-preflight.test.ts +289 -0
- package/template/apps/server/src/module-preflight.ts +96 -0
- package/template/apps/server/src/module-runtime-validation.ts +1 -0
- package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
- package/template/apps/server/src/platform-http-runtime.ts +32 -7
- package/template/apps/server/src/route-guards.test.ts +22 -0
- package/template/apps/server/src/route-guards.ts +10 -3
- package/template/apps/server/src/runtime-audit-query.ts +50 -0
- package/template/apps/server/src/runtime-feature.test.ts +46 -0
- package/template/apps/server/src/runtime-feature.ts +5 -0
- package/template/apps/server/src/runtime.ts +10 -49
- package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
- package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
- package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
- package/template/apps/server/src/scheduler/service.test.ts +1 -0
- package/template/apps/server/src/scheduler/service.ts +18 -21
- package/template/apps/server/src/scheduler/types.ts +5 -1
- package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
- package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
- package/template/apps/server/src/settings/watermark-routes.ts +29 -1
- package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
- package/template/apps/server/src/startup-composition-validation.ts +54 -0
- package/template/apps/server/src/startup-preflight.test.ts +36 -0
- package/template/apps/server/src/startup-preflight.ts +37 -0
- package/template/apps/server/src/system/audit-routes.test.ts +9 -3
- package/template/apps/server/src/system/department-routes.test.ts +5 -3
- package/template/apps/server/src/system/feature-routes.test.ts +4 -3
- package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
- package/template/apps/server/src/system/interface-catalog.ts +3 -0
- package/template/apps/server/src/system/menu-routes.test.ts +5 -3
- package/template/apps/server/src/system/route-test-runtime.ts +24 -0
- package/template/apps/server/src/system/routes-validation.test.ts +7 -3
- package/template/apps/server/src/system/routes.test.ts +21 -11
- package/template/apps/server/src/system/routes.ts +11 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
- package/template/apps/server/src/work-order/routes.test.ts +8 -0
- package/template/apps/server/src/work-order/task.test.ts +6 -1
- package/template/apps/web/Dockerfile +5 -0
- package/template/apps/web/package.json +1 -0
- package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
- package/template/apps/web/src/app/web-composition.ts +24 -0
- package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
- package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
- package/template/apps/web/src/composables/watermark-settings.ts +40 -11
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
- package/template/apps/web/src/layout/AppSidebar.vue +8 -1
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
- package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
- package/template/apps/web/src/layout/navigation.ts +29 -4
- package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
- package/template/apps/web/src/pages/settings/WatermarkPreview.vue +72 -0
- package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
- package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
- package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
- package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
- package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
- package/template/apps/web/src/router/business-routes.ts +32 -45
- package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
- package/template/apps/web/src/router/index.ts +64 -54
- package/template/apps/web/src/router/manifest-routes.ts +27 -7
- package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
- package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
- package/template/apps/web/src/router/web-composition-host.ts +20 -0
- package/template/apps/web/src/services/work-order-api.ts +16 -0
- package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
- package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
- package/template/apps/web/vite.config.ts +22 -1
- package/template/docs/architecture/migration-bundle.md +10 -7
- package/template/docs/architecture/module-manifest-composition.md +85 -3
- package/template/docs/architecture/search-provider-protocol.md +6 -1
- package/template/docs/architecture/server-module-interface.md +70 -7
- package/template/docs/development/business-web-routes.md +53 -50
- package/template/docs/development/module-lifecycle.md +48 -54
- package/template/docs/platform/navigation-registry.md +5 -4
- package/template/docs/platform/platform-shell-settings.md +15 -10
- package/template/docs/platform/web-system-crud.md +1 -1
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
- package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/schema/platform-settings.ts +3 -0
- package/template/packages/example-work-order/package.json +6 -0
- package/template/packages/example-work-order/src/admin-page.ts +78 -0
- package/template/packages/example-work-order/src/admin.ts +35 -0
- package/template/packages/example-work-order/src/manifest.test.ts +62 -5
- package/template/packages/example-work-order/src/manifest.ts +54 -7
- package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
- package/template/packages/example-work-order/src/server-module.ts +2 -2
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/index.ts +4 -0
- package/template/packages/modules/package.json +3 -2
- package/template/packages/modules/src/admin-ui-entry.ts +8 -0
- package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
- package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
- package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
- package/template/packages/modules/src/capability-audit-validation.ts +38 -0
- package/template/packages/modules/src/capability-guard-validation.ts +148 -0
- package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
- package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
- package/template/packages/modules/src/composition.test.ts +74 -9
- package/template/packages/modules/src/composition.ts +8 -0
- package/template/packages/modules/src/manifest.ts +1 -0
- package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
- package/template/packages/modules/src/module-host-range.test.ts +57 -0
- package/template/packages/modules/src/module-host-range.ts +125 -0
- package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
- package/template/packages/modules/src/project-module-assembly.ts +126 -0
- package/template/packages/modules/src/system-admin-routes.ts +6 -5
- package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
- package/template/packages/modules/src/system-agent-tools.ts +2 -2
- package/template/packages/modules/src/system-api-binding.ts +43 -9
- package/template/packages/modules/src/system-api-control-plane.ts +202 -0
- package/template/packages/modules/src/system-api-data-governance.ts +4 -2
- package/template/packages/modules/src/system-api-operational.ts +57 -8
- package/template/packages/modules/src/system-api-permissions.ts +115 -21
- package/template/packages/modules/src/system-api-watermark.ts +3 -0
- package/template/packages/modules/src/system-audit-actions.ts +7 -0
- package/template/packages/modules/src/system-data-governance.ts +5 -2
- package/template/packages/modules/src/system-features.ts +31 -0
- package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
- package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
- package/template/packages/modules/src/system-module-catalog.ts +68 -7
- package/template/packages/modules/src/system-module-tasks.ts +8 -1
- package/template/packages/modules/src/system-modules-access.test.ts +4 -0
- package/template/packages/modules/src/system-modules.test.ts +10 -2
- package/template/packages/modules/src/system-modules.ts +6 -5
- package/template/packages/modules/src/system-permissions.ts +4 -0
- package/template/packages/modules/user/module.ts +19 -2
- package/template/packages/server-sdk/index.ts +8 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/durable-handler.ts +16 -1
- package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
- package/template/packages/server-sdk/src/module-adapter.ts +59 -0
- package/template/packages/server-sdk/src/route-definition.ts +3 -2
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.test.ts +7 -0
- package/template/packages/shared/src/audit.ts +16 -0
- package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
- package/template/packages/shared/src/platform-settings.ts +17 -0
- package/template/packages/shared/src/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUIDv7 } from "bun";
|
|
2
2
|
import type { SchedulerOperationsSnapshot } from "@southwind-ai/shared";
|
|
3
3
|
import { runGuardedBackgroundTask } from "../background/task-guard.js";
|
|
4
|
-
import type { RequestGuardContext } from "../guards.js";
|
|
4
|
+
import type { RequestActor, RequestGuardContext } from "../guards.js";
|
|
5
5
|
import type { LegacyDurableAdapter } from "../jobs/legacy-adapter.js";
|
|
6
6
|
import type { createServerRuntime } from "../runtime.js";
|
|
7
7
|
import type { SchedulerRepository } from "./repository.js";
|
|
@@ -41,6 +41,9 @@ export class SchedulerService {
|
|
|
41
41
|
private readonly runtime: ServerRuntime,
|
|
42
42
|
private readonly clock: SchedulerClock = systemClock,
|
|
43
43
|
private readonly durable?: LegacyDurableAdapter,
|
|
44
|
+
private readonly resolveActor?: (
|
|
45
|
+
actorId: string,
|
|
46
|
+
) => Promise<RequestActor | undefined>,
|
|
44
47
|
) {
|
|
45
48
|
this.recorder = new SchedulerExecutionRecorder(repository, runtime, clock);
|
|
46
49
|
validateTaskDefinitions(definitions);
|
|
@@ -196,13 +199,14 @@ export class SchedulerService {
|
|
|
196
199
|
await this.executeScheduled(run, false);
|
|
197
200
|
return;
|
|
198
201
|
}
|
|
199
|
-
await this.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const actor = await this.resolveActor?.(run.requestedBy);
|
|
203
|
+
const context = await this.runtime.createGuardContextForActor(
|
|
204
|
+
new Request(
|
|
205
|
+
`http://scheduler.local/tasks/${encodeURIComponent(run.taskKey)}/resume`,
|
|
206
|
+
),
|
|
207
|
+
actor ?? deniedResumeActor(run.requestedBy),
|
|
205
208
|
);
|
|
209
|
+
await this.execute(run, definition, context, true, false);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
private async executeScheduled(run: ScheduledTaskRun, useDurable = true) {
|
|
@@ -262,7 +266,7 @@ export class SchedulerService {
|
|
|
262
266
|
finishedAt: this.clock.now(),
|
|
263
267
|
error: guarded.reason,
|
|
264
268
|
});
|
|
265
|
-
this.recorder.denied(context, blocked, guarded.reason);
|
|
269
|
+
this.recorder.denied(context, blocked, definition, guarded.reason);
|
|
266
270
|
return blocked;
|
|
267
271
|
}
|
|
268
272
|
return this.recorder.success(run, definition, context, guarded.value);
|
|
@@ -321,19 +325,12 @@ export class SchedulerService {
|
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
|
|
324
|
-
function
|
|
325
|
-
run: ScheduledTaskRun,
|
|
326
|
-
definition: ScheduledTaskDefinition,
|
|
327
|
-
): RequestGuardContext {
|
|
328
|
+
function deniedResumeActor(actorId: string): RequestActor {
|
|
328
329
|
return {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
roleCodes: [],
|
|
335
|
-
permissionKeys: [definition.permissionKey],
|
|
336
|
-
},
|
|
337
|
-
startedAt: new Date().toISOString(),
|
|
330
|
+
id: actorId,
|
|
331
|
+
type: "user",
|
|
332
|
+
name: actorId,
|
|
333
|
+
roleCodes: [],
|
|
334
|
+
permissionKeys: [],
|
|
338
335
|
};
|
|
339
336
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AuditActionType,
|
|
3
|
+
FeatureFlagDefinition,
|
|
4
|
+
} from "@southwind-ai/shared";
|
|
2
5
|
|
|
3
6
|
export interface SchedulerClock {
|
|
4
7
|
now(): Date;
|
|
@@ -20,6 +23,7 @@ export interface ScheduledTaskDefinition {
|
|
|
20
23
|
maxAttempts: number;
|
|
21
24
|
retryDelaySeconds: number;
|
|
22
25
|
permissionKey: string;
|
|
26
|
+
auditAction: AuditActionType;
|
|
23
27
|
feature: FeatureFlagDefinition;
|
|
24
28
|
handler: ScheduledTaskHandler;
|
|
25
29
|
isIdleResult?: (value: Record<string, unknown> | void) => boolean;
|
|
@@ -42,6 +42,8 @@ describePostgres("PostgreSQL watermark policy repository", () => {
|
|
|
42
42
|
density: "high",
|
|
43
43
|
contentFields: ["username", "phone"],
|
|
44
44
|
customContent: "内部资料",
|
|
45
|
+
opacityPercent: 16,
|
|
46
|
+
fontSize: 20,
|
|
45
47
|
},
|
|
46
48
|
"user_settings",
|
|
47
49
|
);
|
|
@@ -56,6 +58,8 @@ describePostgres("PostgreSQL watermark policy repository", () => {
|
|
|
56
58
|
density: "high",
|
|
57
59
|
contentFields: ["username", "phone"],
|
|
58
60
|
customContent: "内部资料",
|
|
61
|
+
opacityPercent: 16,
|
|
62
|
+
fontSize: 20,
|
|
59
63
|
});
|
|
60
64
|
});
|
|
61
65
|
});
|
|
@@ -30,6 +30,7 @@ export class DrizzleWatermarkPolicyRepository implements WatermarkPolicyReposito
|
|
|
30
30
|
.values({
|
|
31
31
|
id: PLATFORM_WATERMARK_ID,
|
|
32
32
|
...policy,
|
|
33
|
+
businessPagesEnabled: policy.enabled,
|
|
33
34
|
createdBy: actorId,
|
|
34
35
|
updatedBy: actorId,
|
|
35
36
|
})
|
|
@@ -37,10 +38,12 @@ export class DrizzleWatermarkPolicyRepository implements WatermarkPolicyReposito
|
|
|
37
38
|
target: platformWatermarkPolicies.id,
|
|
38
39
|
set: {
|
|
39
40
|
enabled: policy.enabled,
|
|
40
|
-
businessPagesEnabled: policy.
|
|
41
|
+
businessPagesEnabled: policy.enabled,
|
|
41
42
|
density: policy.density,
|
|
42
43
|
contentFields: policy.contentFields,
|
|
43
44
|
customContent: policy.customContent,
|
|
45
|
+
opacityPercent: policy.opacityPercent,
|
|
46
|
+
fontSize: policy.fontSize,
|
|
44
47
|
updatedAt: new Date(),
|
|
45
48
|
updatedBy: actorId,
|
|
46
49
|
},
|
|
@@ -57,12 +60,17 @@ function toPolicy(row: {
|
|
|
57
60
|
density: PlatformWatermarkPolicy["density"];
|
|
58
61
|
contentFields: PlatformWatermarkPolicy["contentFields"];
|
|
59
62
|
customContent: string;
|
|
63
|
+
opacityPercent: number;
|
|
64
|
+
fontSize: number;
|
|
60
65
|
}): PlatformWatermarkPolicy {
|
|
66
|
+
const enabled = row.enabled && row.businessPagesEnabled;
|
|
61
67
|
return {
|
|
62
|
-
enabled
|
|
63
|
-
businessPagesEnabled:
|
|
68
|
+
enabled,
|
|
69
|
+
businessPagesEnabled: enabled,
|
|
64
70
|
density: row.density,
|
|
65
71
|
contentFields: row.contentFields,
|
|
66
72
|
customContent: row.customContent,
|
|
73
|
+
opacityPercent: row.opacityPercent,
|
|
74
|
+
fontSize: row.fontSize,
|
|
67
75
|
};
|
|
68
76
|
}
|
|
@@ -47,6 +47,8 @@ describe("平台水印策略路由", () => {
|
|
|
47
47
|
density: "medium",
|
|
48
48
|
contentFields: ["username", "displayName"],
|
|
49
49
|
customContent: "",
|
|
50
|
+
opacityPercent: 8,
|
|
51
|
+
fontSize: 14,
|
|
50
52
|
});
|
|
51
53
|
});
|
|
52
54
|
|
|
@@ -58,6 +60,8 @@ describe("平台水印策略路由", () => {
|
|
|
58
60
|
density: "high",
|
|
59
61
|
contentFields: ["username", "phone", "custom"],
|
|
60
62
|
customContent: "内部资料",
|
|
63
|
+
opacityPercent: 16,
|
|
64
|
+
fontSize: 20,
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
const updated = (await fixture.app.invoke(
|
|
@@ -83,7 +87,13 @@ describe("平台水印策略路由", () => {
|
|
|
83
87
|
action: "config.update",
|
|
84
88
|
target: { type: "platform-watermark", id: "platform" },
|
|
85
89
|
metadata: expect.objectContaining({
|
|
86
|
-
changedFields: [
|
|
90
|
+
changedFields: [
|
|
91
|
+
"density",
|
|
92
|
+
"contentFields",
|
|
93
|
+
"customContent",
|
|
94
|
+
"opacityPercent",
|
|
95
|
+
"fontSize",
|
|
96
|
+
],
|
|
87
97
|
}),
|
|
88
98
|
}),
|
|
89
99
|
]),
|
|
@@ -104,6 +114,8 @@ describe("平台水印策略路由", () => {
|
|
|
104
114
|
density: "low",
|
|
105
115
|
contentFields: ["username"],
|
|
106
116
|
customContent: "",
|
|
117
|
+
opacityPercent: 8,
|
|
118
|
+
fontSize: 14,
|
|
107
119
|
})) as Response;
|
|
108
120
|
|
|
109
121
|
expect(readable.enabled).toBe(true);
|
|
@@ -139,6 +151,8 @@ describe("平台水印策略路由", () => {
|
|
|
139
151
|
density: "medium",
|
|
140
152
|
contentFields: ["username"],
|
|
141
153
|
customContent: "x".repeat(81),
|
|
154
|
+
opacityPercent: 8,
|
|
155
|
+
fontSize: 14,
|
|
142
156
|
})) as Response;
|
|
143
157
|
|
|
144
158
|
expect(response.status).toBe(400);
|
|
@@ -148,9 +162,48 @@ describe("平台水印策略路由", () => {
|
|
|
148
162
|
density: "medium",
|
|
149
163
|
contentFields: ["username", "displayName"],
|
|
150
164
|
customContent: "",
|
|
165
|
+
opacityPercent: 8,
|
|
166
|
+
fontSize: 14,
|
|
151
167
|
});
|
|
152
168
|
expect(fixture.runtime.auditSink.list()).toHaveLength(0);
|
|
153
169
|
});
|
|
170
|
+
|
|
171
|
+
test("旧业务范围状态会折叠到唯一开关", async () => {
|
|
172
|
+
const fixture = await createFixture(settingsActor(true));
|
|
173
|
+
|
|
174
|
+
const updated = (await update(fixture, {
|
|
175
|
+
enabled: true,
|
|
176
|
+
businessPagesEnabled: false,
|
|
177
|
+
density: "medium",
|
|
178
|
+
contentFields: ["email", "displayName"],
|
|
179
|
+
customContent: "",
|
|
180
|
+
opacityPercent: 8,
|
|
181
|
+
fontSize: 14,
|
|
182
|
+
})) as PlatformWatermarkPolicy;
|
|
183
|
+
|
|
184
|
+
expect(updated.enabled).toBe(true);
|
|
185
|
+
expect(updated.businessPagesEnabled).toBe(true);
|
|
186
|
+
expect(updated.contentFields).toEqual(["email", "displayName"]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("不透明度和字体大小超出范围时拒绝保存", async () => {
|
|
190
|
+
const fixture = await createFixture(settingsActor(true));
|
|
191
|
+
|
|
192
|
+
const response = (await update(fixture, {
|
|
193
|
+
enabled: true,
|
|
194
|
+
businessPagesEnabled: true,
|
|
195
|
+
density: "medium",
|
|
196
|
+
contentFields: ["username"],
|
|
197
|
+
customContent: "",
|
|
198
|
+
opacityPercent: 31,
|
|
199
|
+
fontSize: 14,
|
|
200
|
+
})) as Response;
|
|
201
|
+
|
|
202
|
+
expect(response.status).toBe(400);
|
|
203
|
+
await expect(response.json()).resolves.toMatchObject({
|
|
204
|
+
error: "INVALID_WATERMARK_POLICY",
|
|
205
|
+
});
|
|
206
|
+
});
|
|
154
207
|
});
|
|
155
208
|
|
|
156
209
|
async function update(
|
|
@@ -6,6 +6,8 @@ import type {
|
|
|
6
6
|
import {
|
|
7
7
|
WATERMARK_CONTENT_FIELDS,
|
|
8
8
|
WATERMARK_DENSITIES,
|
|
9
|
+
WATERMARK_FONT_SIZE_RANGE,
|
|
10
|
+
WATERMARK_OPACITY_RANGE,
|
|
9
11
|
} from "@southwind-ai/shared";
|
|
10
12
|
import { evaluateRouteGuards, findFeature } from "../route-guards.js";
|
|
11
13
|
import type { createServerRuntime } from "../runtime.js";
|
|
@@ -137,6 +139,28 @@ function parseWatermarkPolicy(body: unknown): WatermarkParseResult {
|
|
|
137
139
|
if (customContent.length > 80) {
|
|
138
140
|
return { ok: false, message: "自定义内容不能超过 80 个字符" };
|
|
139
141
|
}
|
|
142
|
+
if (
|
|
143
|
+
typeof source.opacityPercent !== "number" ||
|
|
144
|
+
!Number.isInteger(source.opacityPercent) ||
|
|
145
|
+
source.opacityPercent < WATERMARK_OPACITY_RANGE.min ||
|
|
146
|
+
source.opacityPercent > WATERMARK_OPACITY_RANGE.max
|
|
147
|
+
) {
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
message: `水印不透明度必须是 ${WATERMARK_OPACITY_RANGE.min}% 到 ${WATERMARK_OPACITY_RANGE.max}% 的整数`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
typeof source.fontSize !== "number" ||
|
|
155
|
+
!Number.isInteger(source.fontSize) ||
|
|
156
|
+
source.fontSize < WATERMARK_FONT_SIZE_RANGE.min ||
|
|
157
|
+
source.fontSize > WATERMARK_FONT_SIZE_RANGE.max
|
|
158
|
+
) {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
message: `水印字体大小必须是 ${WATERMARK_FONT_SIZE_RANGE.min}px 到 ${WATERMARK_FONT_SIZE_RANGE.max}px 的整数`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
140
164
|
const contentFields = [
|
|
141
165
|
...new Set(
|
|
142
166
|
source.contentFields as PlatformWatermarkPolicy["contentFields"],
|
|
@@ -146,10 +170,12 @@ function parseWatermarkPolicy(body: unknown): WatermarkParseResult {
|
|
|
146
170
|
ok: true,
|
|
147
171
|
value: {
|
|
148
172
|
enabled: source.enabled,
|
|
149
|
-
businessPagesEnabled: source.
|
|
173
|
+
businessPagesEnabled: source.enabled,
|
|
150
174
|
density: source.density as PlatformWatermarkPolicy["density"],
|
|
151
175
|
contentFields,
|
|
152
176
|
customContent,
|
|
177
|
+
opacityPercent: source.opacityPercent,
|
|
178
|
+
fontSize: source.fontSize,
|
|
153
179
|
},
|
|
154
180
|
};
|
|
155
181
|
}
|
|
@@ -164,6 +190,8 @@ function changedFields(
|
|
|
164
190
|
"density",
|
|
165
191
|
"contentFields",
|
|
166
192
|
"customContent",
|
|
193
|
+
"opacityPercent",
|
|
194
|
+
"fontSize",
|
|
167
195
|
].filter(
|
|
168
196
|
(field) =>
|
|
169
197
|
JSON.stringify(before[field as keyof PlatformWatermarkPolicy]) !==
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateCompositionBeforeStartup } from "./startup-composition-validation.js";
|
|
3
|
+
|
|
4
|
+
describe("完整 Server Composition 启动前门禁", () => {
|
|
5
|
+
test("正常启动先执行无数据库验证子进程", () => {
|
|
6
|
+
let databaseUrlSeen = "not-called";
|
|
7
|
+
validateCompositionBeforeStartup(
|
|
8
|
+
["bun", "src/index.ts"],
|
|
9
|
+
(_command, _args, options) => {
|
|
10
|
+
databaseUrlSeen = options.env.DATABASE_URL ?? "";
|
|
11
|
+
return { status: 0 };
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(databaseUrlSeen).toBe("");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("验证子进程自身不递归,并把失败传回启动进程", () => {
|
|
19
|
+
let calls = 0;
|
|
20
|
+
validateCompositionBeforeStartup(
|
|
21
|
+
["bun", "src/index.ts", "--validate-module-composition"],
|
|
22
|
+
() => {
|
|
23
|
+
calls += 1;
|
|
24
|
+
return { status: 0 };
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
expect(calls).toBe(0);
|
|
28
|
+
|
|
29
|
+
expect(() =>
|
|
30
|
+
validateCompositionBeforeStartup(["bun", "src/index.ts"], () => ({
|
|
31
|
+
status: 1,
|
|
32
|
+
})),
|
|
33
|
+
).toThrow("启动前校验失败");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
const VALIDATION_ARGUMENT = "--validate-module-composition";
|
|
5
|
+
const validatorPath = fileURLToPath(
|
|
6
|
+
new URL("../scripts/validate-module-composition.ts", import.meta.url),
|
|
7
|
+
);
|
|
8
|
+
const serverRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
9
|
+
|
|
10
|
+
interface ValidationProcessResult {
|
|
11
|
+
readonly status: number | null;
|
|
12
|
+
readonly error?: Error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type SpawnValidationProcess = (
|
|
16
|
+
command: string,
|
|
17
|
+
args: readonly string[],
|
|
18
|
+
options: {
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly env: NodeJS.ProcessEnv;
|
|
21
|
+
readonly stdio: "inherit";
|
|
22
|
+
},
|
|
23
|
+
) => ValidationProcessResult;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 正常启动先用无数据库子进程走一遍与 build 相同的完整路由/任务/Provider 门禁。
|
|
27
|
+
*/
|
|
28
|
+
export function validateCompositionBeforeStartup(
|
|
29
|
+
argv: readonly string[] = process.argv,
|
|
30
|
+
spawn: SpawnValidationProcess = spawnValidationProcess,
|
|
31
|
+
): void {
|
|
32
|
+
if (argv.includes(VALIDATION_ARGUMENT)) return;
|
|
33
|
+
const env = { ...process.env };
|
|
34
|
+
delete env.DATABASE_URL;
|
|
35
|
+
const result = spawn(process.execPath, [validatorPath], {
|
|
36
|
+
cwd: serverRoot,
|
|
37
|
+
env,
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
});
|
|
40
|
+
if (result.error) throw result.error;
|
|
41
|
+
if (result.status !== 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Server 完整 Module Composition 启动前校验失败(exit ${result.status ?? "unknown"})`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function spawnValidationProcess(
|
|
49
|
+
command: string,
|
|
50
|
+
args: readonly string[],
|
|
51
|
+
options: Parameters<SpawnValidationProcess>[2],
|
|
52
|
+
): ValidationProcessResult {
|
|
53
|
+
return spawnSync(command, [...args], options);
|
|
54
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { runPreflightBeforeRecovery } from "./startup-preflight.js";
|
|
3
|
+
|
|
4
|
+
describe("Server 启动前门禁顺序", () => {
|
|
5
|
+
test("先完成无持久化 preflight,再恢复审计队列", async () => {
|
|
6
|
+
const calls: string[] = [];
|
|
7
|
+
const result = await runPreflightBeforeRecovery(
|
|
8
|
+
() => {
|
|
9
|
+
calls.push("prepare");
|
|
10
|
+
return Promise.resolve("ready");
|
|
11
|
+
},
|
|
12
|
+
() => {
|
|
13
|
+
calls.push("recover");
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(result).toBe("ready");
|
|
19
|
+
expect(calls).toEqual(["prepare", "recover"]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("preflight 失败时不触发恢复写入", async () => {
|
|
23
|
+
let recovered = false;
|
|
24
|
+
await expect(
|
|
25
|
+
runPreflightBeforeRecovery(
|
|
26
|
+
() => Promise.reject(new Error("invalid composition")),
|
|
27
|
+
() => {
|
|
28
|
+
recovered = true;
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
).rejects.toThrow("invalid composition");
|
|
33
|
+
|
|
34
|
+
expect(recovered).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ModuleUploadPolicy } from "@southwind-ai/server-sdk";
|
|
2
|
+
import { prepareInstalledServerModules } from "./module-preflight.js";
|
|
3
|
+
import { createPlatformBootstrap } from "./platform-bootstrap.js";
|
|
4
|
+
// @windy-module system.storage begin
|
|
5
|
+
import { createPlatformUploadPolicies } from "./storage/runtime.js";
|
|
6
|
+
// @windy-module system.storage end
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 在恢复审计队列、执行迁移或写入目录前,完成项目装配、Host 与 attachment 门禁。
|
|
10
|
+
*/
|
|
11
|
+
export async function createPreparedPlatformBootstrap() {
|
|
12
|
+
const bootstrap = createPlatformBootstrap();
|
|
13
|
+
const baseUploadPolicies: ModuleUploadPolicy[] = [
|
|
14
|
+
// @windy-module system.storage begin
|
|
15
|
+
...createPlatformUploadPolicies(bootstrap.platformConfig),
|
|
16
|
+
// @windy-module system.storage end
|
|
17
|
+
];
|
|
18
|
+
const recoverAuditQueue = () =>
|
|
19
|
+
// @windy-module system.audit begin
|
|
20
|
+
bootstrap.persistence?.auditWriter.recover() ??
|
|
21
|
+
// @windy-module system.audit end
|
|
22
|
+
Promise.resolve();
|
|
23
|
+
const preparedModules = await runPreflightBeforeRecovery(
|
|
24
|
+
() => prepareInstalledServerModules(baseUploadPolicies),
|
|
25
|
+
recoverAuditQueue,
|
|
26
|
+
);
|
|
27
|
+
return { ...bootstrap, preparedModules };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runPreflightBeforeRecovery<T>(
|
|
31
|
+
prepare: () => Promise<T>,
|
|
32
|
+
recover: () => Promise<void>,
|
|
33
|
+
): Promise<T> {
|
|
34
|
+
const prepared = await prepare();
|
|
35
|
+
await recover();
|
|
36
|
+
return prepared;
|
|
37
|
+
}
|
|
@@ -6,12 +6,14 @@ import { registerSystemRoutes } from "./routes.js";
|
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
8
8
|
async function auditFixture() {
|
|
9
|
+
const snapshot = createFoundationSnapshot();
|
|
9
10
|
const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token_1" });
|
|
10
11
|
const app = new FakeRouteApp();
|
|
11
12
|
registerSystemRoutes(
|
|
12
13
|
app,
|
|
13
|
-
createSystemRepositories(
|
|
14
|
+
createSystemRepositories(snapshot),
|
|
14
15
|
runtime,
|
|
16
|
+
snapshot.features,
|
|
15
17
|
);
|
|
16
18
|
const guardContext = await runtime.createGuardContext(
|
|
17
19
|
new Request("http://localhost/api/system/audit-logs", {
|
|
@@ -54,10 +56,12 @@ describe("audit routes", () => {
|
|
|
54
56
|
},
|
|
55
57
|
);
|
|
56
58
|
const app = new FakeRouteApp();
|
|
59
|
+
const snapshot = createFoundationSnapshot();
|
|
57
60
|
registerSystemRoutes(
|
|
58
61
|
app,
|
|
59
|
-
createSystemRepositories(
|
|
62
|
+
createSystemRepositories(snapshot),
|
|
60
63
|
runtime,
|
|
64
|
+
snapshot.features,
|
|
61
65
|
);
|
|
62
66
|
const guardContext = await runtime.createGuardContext(
|
|
63
67
|
new Request("http://localhost/api/system/audit-logs", {
|
|
@@ -108,10 +112,12 @@ describe("audit routes", () => {
|
|
|
108
112
|
},
|
|
109
113
|
);
|
|
110
114
|
const app = new FakeRouteApp();
|
|
115
|
+
const snapshot = createFoundationSnapshot();
|
|
111
116
|
registerSystemRoutes(
|
|
112
117
|
app,
|
|
113
|
-
createSystemRepositories(
|
|
118
|
+
createSystemRepositories(snapshot),
|
|
114
119
|
runtime,
|
|
120
|
+
snapshot.features,
|
|
115
121
|
);
|
|
116
122
|
const guardContext = await runtime.createGuardContext(
|
|
117
123
|
new Request("http://localhost/api/system/audit-logs", {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
8
8
|
async function fixture() {
|
|
9
9
|
const snapshot = createFoundationSnapshot();
|
|
10
10
|
const repositories = createSystemRepositories(snapshot);
|
|
11
|
-
const runtime =
|
|
11
|
+
const runtime = createRouteTestRuntime(snapshot, {
|
|
12
|
+
WINDY_DEV_ADMIN_TOKEN: "token_1",
|
|
13
|
+
});
|
|
12
14
|
const app = new FakeRouteApp();
|
|
13
15
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
14
16
|
const guardContext = await runtime.createGuardContext(
|
|
@@ -27,7 +29,7 @@ async function fixture() {
|
|
|
27
29
|
describe("部门组织路由", () => {
|
|
28
30
|
test("部门处置缺少权限时经过统一 Guard 拒绝", async () => {
|
|
29
31
|
const snapshot = createFoundationSnapshot();
|
|
30
|
-
const runtime =
|
|
32
|
+
const runtime = createRouteTestRuntime(snapshot);
|
|
31
33
|
const app = new FakeRouteApp();
|
|
32
34
|
registerSystemRoutes(
|
|
33
35
|
app,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
@@ -10,7 +10,8 @@ describe("feature routes", () => {
|
|
|
10
10
|
const snapshot = createFoundationSnapshot();
|
|
11
11
|
const repositories = createSystemRepositories(snapshot);
|
|
12
12
|
await repositories.features.syncManifest(snapshot.features);
|
|
13
|
-
const runtime =
|
|
13
|
+
const runtime = createRouteTestRuntime(
|
|
14
|
+
snapshot,
|
|
14
15
|
{ WINDY_DEV_ADMIN_TOKEN: "token_1" },
|
|
15
16
|
{ featureResolver: repositories.features },
|
|
16
17
|
);
|
|
@@ -46,7 +47,7 @@ describe("feature routes", () => {
|
|
|
46
47
|
});
|
|
47
48
|
expect(denied.status).toBe(403);
|
|
48
49
|
await expect(denied.json()).resolves.toMatchObject({
|
|
49
|
-
reason: "
|
|
50
|
+
reason: "dependency-missing",
|
|
50
51
|
});
|
|
51
52
|
expect(app.handlers.has("POST /system/features")).toBe(false);
|
|
52
53
|
expect(app.handlers.has("POST /system/features/import")).toBe(false);
|
|
@@ -15,7 +15,8 @@ describe("系统接口权限目录", () => {
|
|
|
15
15
|
),
|
|
16
16
|
).toMatchObject({
|
|
17
17
|
permissionKey: "system.user.manage",
|
|
18
|
-
featureKey: "system.
|
|
18
|
+
featureKey: "system.user",
|
|
19
|
+
auditAction: "crud.create",
|
|
19
20
|
guardOrder: ["license", "feature", "permission"],
|
|
20
21
|
source: "module-manifest",
|
|
21
22
|
});
|
|
@@ -52,11 +53,12 @@ describe("系统接口权限目录", () => {
|
|
|
52
53
|
),
|
|
53
54
|
).toMatchObject({
|
|
54
55
|
permissionKey: "system.feature.manage",
|
|
55
|
-
|
|
56
|
+
featureKey: "system.feature",
|
|
57
|
+
guardOrder: ["license", "feature", "permission"],
|
|
56
58
|
});
|
|
57
59
|
expect(
|
|
58
60
|
interfaces.find((item) => item.path === "/api/foundation"),
|
|
59
|
-
).toMatchObject({ anonymous: true });
|
|
61
|
+
).toMatchObject({ anonymous: true, auditAction: undefined });
|
|
60
62
|
// @windy-module system.bulk-data begin
|
|
61
63
|
expect(
|
|
62
64
|
interfaces.find(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
2
2
|
import type {
|
|
3
|
+
AuditActionType,
|
|
3
4
|
EntitlementKey,
|
|
4
5
|
HttpMethod,
|
|
5
6
|
LicenseVersionKey,
|
|
@@ -19,6 +20,7 @@ export interface SystemInterfacePermissionDetail {
|
|
|
19
20
|
permissionKey: string;
|
|
20
21
|
anonymous: boolean;
|
|
21
22
|
featureKey?: string;
|
|
23
|
+
auditAction?: AuditActionType;
|
|
22
24
|
allowedLicenseVersions?: readonly LicenseVersionKey[];
|
|
23
25
|
entitlementKey?: EntitlementKey;
|
|
24
26
|
guardOrder: readonly GuardStep[];
|
|
@@ -56,6 +58,7 @@ export function collectSystemInterfacePermissions(
|
|
|
56
58
|
permissionKey: binding.permissionKey,
|
|
57
59
|
anonymous: binding.anonymous === true,
|
|
58
60
|
featureKey: feature?.key,
|
|
61
|
+
auditAction: binding.auditAction,
|
|
59
62
|
allowedLicenseVersions: feature?.allowedLicenseVersions,
|
|
60
63
|
entitlementKey: feature?.requiredEntitlement,
|
|
61
64
|
guardOrder: resolveGuardOrder(feature),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
@@ -9,7 +9,9 @@ describe("菜单结构批量路由", () => {
|
|
|
9
9
|
test("完整草稿通过 system.menu.manage Guard 后一次保存并写审计", async () => {
|
|
10
10
|
const snapshot = createFoundationSnapshot();
|
|
11
11
|
const repositories = createSystemRepositories(snapshot);
|
|
12
|
-
const runtime =
|
|
12
|
+
const runtime = createRouteTestRuntime(snapshot, {
|
|
13
|
+
WINDY_DEV_ADMIN_TOKEN: "token_1",
|
|
14
|
+
});
|
|
13
15
|
const app = new FakeRouteApp();
|
|
14
16
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
15
17
|
const guardContext = await runtime.createGuardContext(
|
|
@@ -49,7 +51,7 @@ describe("菜单结构批量路由", () => {
|
|
|
49
51
|
test("缺少管理权限时拒绝保存菜单草稿", async () => {
|
|
50
52
|
const snapshot = createFoundationSnapshot();
|
|
51
53
|
const repositories = createSystemRepositories(snapshot);
|
|
52
|
-
const runtime =
|
|
54
|
+
const runtime = createRouteTestRuntime(snapshot);
|
|
53
55
|
const app = new FakeRouteApp();
|
|
54
56
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
55
57
|
const guardContext = await runtime.createGuardContext(
|