create-windy 0.2.16 → 0.2.18

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 (69) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.js +71 -22
  3. package/package.json +1 -1
  4. package/template/.dockerignore +5 -0
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +11 -1
  7. package/template/README.md +8 -5
  8. package/template/_gitignore +5 -0
  9. package/template/apps/server/Dockerfile +4 -5
  10. package/template/apps/server/src/index.ts +8 -14
  11. package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +57 -0
  12. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +62 -0
  13. package/template/apps/server/src/settings/runtime.ts +30 -3
  14. package/template/apps/server/src/settings/watermark-repository.ts +32 -0
  15. package/template/apps/server/src/settings/watermark-routes.test.ts +203 -0
  16. package/template/apps/server/src/settings/watermark-routes.ts +134 -0
  17. package/template/apps/web/Dockerfile +1 -2
  18. package/template/apps/web/runtime-server.test.ts +4 -4
  19. package/template/apps/web/src/composables/useWatermarkSettings.ts +75 -33
  20. package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +86 -0
  21. package/template/apps/web/src/composables/watermark-settings.ts +9 -35
  22. package/template/apps/web/src/composables/watermark-settings.webtest.ts +8 -32
  23. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +81 -0
  24. package/template/apps/web/src/layout/GlobalWatermark.vue +34 -31
  25. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +58 -7
  26. package/template/apps/web/src/pages/settings/GlobalSettingsPage.vue +1 -1
  27. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +40 -18
  28. package/template/apps/web/src/services/platform-settings-api.ts +25 -2
  29. package/template/apps/web/vite.config.ts +3 -1
  30. package/template/docker-compose.yml +19 -6
  31. package/template/docs/platform/platform-shell-settings.md +24 -3
  32. package/template/docs/platform/web-system-crud.md +2 -2
  33. package/template/drizzle.config.ts +1 -1
  34. package/template/package.json +6 -2
  35. package/template/packages/ai-client/index.ts +1 -0
  36. package/template/packages/{agent-client → ai-client}/package.json +4 -5
  37. package/template/packages/{agent-client/src → ai-client/src/agent/client}/client.test.ts +3 -3
  38. package/template/packages/{agent-client/src → ai-client/src/agent/client}/client.ts +2 -2
  39. package/template/packages/{agent-client/src → ai-client/src/agent/client}/error.ts +1 -4
  40. package/template/packages/ai-client/src/agent/client/index.ts +3 -0
  41. package/template/packages/{agent-client/src → ai-client/src/agent/client}/run-protocol.test.ts +2 -2
  42. package/template/packages/{agent-client/src → ai-client/src/agent/client}/run.ts +1 -1
  43. package/template/packages/{agent-client/src → ai-client/src/agent/client}/types.ts +1 -1
  44. package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/contracts.test.ts +1 -1
  45. package/template/packages/ai-client/src/agent/contracts/index.ts +7 -0
  46. package/template/packages/ai-client/src/agent/index.ts +14 -0
  47. package/template/packages/{agent-client → ai-client}/tsconfig.json +3 -2
  48. package/template/packages/database/drizzle/0031_panoramic_typhoid_mary.sql +12 -0
  49. package/template/packages/database/drizzle/meta/0031_snapshot.json +7387 -0
  50. package/template/packages/database/drizzle/meta/_journal.json +8 -1
  51. package/template/packages/database/src/schema/platform-settings.ts +16 -1
  52. package/template/packages/modules/src/system-api-permissions.ts +4 -0
  53. package/template/packages/modules/src/system-api-watermark.ts +26 -0
  54. package/template/packages/modules/src/system-features.ts +1 -1
  55. package/template/packages/modules/src/system-watermark-policy.test.ts +31 -0
  56. package/template/packages/shared/src/platform-settings.ts +13 -0
  57. package/template/packages/agent-client/index.ts +0 -3
  58. package/template/packages/agent-contracts/index.ts +0 -7
  59. package/template/packages/agent-contracts/package.json +0 -24
  60. package/template/packages/agent-contracts/tsconfig.json +0 -16
  61. /package/template/packages/{agent-client/src → ai-client/src/agent/client}/event-queue.ts +0 -0
  62. /package/template/packages/{agent-client/src → ai-client/src/agent/client}/http.ts +0 -0
  63. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/errors.ts +0 -0
  64. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/events.ts +0 -0
  65. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/operation.ts +0 -0
  66. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/runs.ts +0 -0
  67. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/sse.ts +0 -0
  68. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/usage.ts +0 -0
  69. /package/template/packages/{agent-contracts/src → ai-client/src/agent/contracts}/validation.ts +0 -0
package/README.md CHANGED
@@ -110,9 +110,10 @@ create-windy 会生成仅供本机使用、被 Git 忽略且权限为 `0600` 的
110
110
  直接打印在终端。首次登录必须修改密码,新密码需要 12-128 个字符,且不能与当前密码
111
111
  相同。
112
112
 
113
- `bun run dev` 会通过 Docker Compose 等待 PostgreSQL、执行 migration,再启动 Server
114
- 与 Web;首次运行前需安装并启动 Docker 与 Docker Compose。环境变量只负责初始化全新
115
- 数据库,数据库已有管理员后,重启服务不会重新覆盖其密码。
113
+ `bun run dev` 会复用已有 Docker 镜像,等待 PostgreSQL、执行 migration,再启动支持
114
+ 热更新的 Server 与 Web;首次缺少镜像时才会构建。依赖、Dockerfile `bun.lock`
115
+ 变化后运行 `bun run dev:build`。首次运行前需安装并启动 Docker 与 Docker Compose。
116
+ 环境变量只负责初始化全新数据库,数据库已有管理员后,重启服务不会重新覆盖其密码。
116
117
 
117
118
  只调试单个应用时可分别使用 `bun run dev:web` 和 `bun run dev:server`。`dev:web` 不会
118
119
  隐式启动后端;`dev:server` 也不会自动启动 PostgreSQL 或执行 migration。
package/dist/cli.js CHANGED
@@ -412,13 +412,20 @@ var platformModules = [
412
412
  "apps/web/src/pages/settings",
413
413
  "apps/web/src/layout/GlobalWatermark.vue",
414
414
  "apps/web/src/layout/GlobalWatermark.webtest.ts",
415
+ "apps/web/src/layout/GlobalWatermark.layering.webtest.ts",
415
416
  "apps/web/src/services/platform-settings-api.ts",
416
417
  "apps/web/src/composables/useWatermarkSettings.ts",
418
+ "apps/web/src/composables/useWatermarkSettings.webtest.ts",
417
419
  "apps/web/src/composables/watermark-settings.ts",
418
- "apps/web/src/composables/watermark-settings.webtest.ts"
420
+ "apps/web/src/composables/watermark-settings.webtest.ts",
421
+ "packages/modules/src/system-api-watermark.ts",
422
+ "packages/modules/src/system-watermark-policy.test.ts"
419
423
  ],
420
424
  documentation: ["docs/platform/platform-shell-settings.md"],
421
- databaseTables: ["platform_branding_settings"]
425
+ databaseTables: [
426
+ "platform_branding_settings",
427
+ "platform_watermark_policies"
428
+ ]
422
429
  },
423
430
  {
424
431
  ...module2("system.configuration", "配置版本", "类型化配置、应用和回滚。", "recommended", ["system"]),
@@ -1305,7 +1312,7 @@ var starterCompose = `services:
1305
1312
  POSTGRES_USER: windy
1306
1313
  POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:?请在 .env 中设置 POSTGRES_PASSWORD}
1307
1314
  ports:
1308
- - "5432:5432"
1315
+ - "15432:5432"
1309
1316
  volumes:
1310
1317
  - postgres-data:/var/lib/postgresql/data
1311
1318
  healthcheck:
@@ -1334,14 +1341,15 @@ var starterCompose = `services:
1334
1341
  command: ["bun", "run", "--cwd", "apps/server", "dev"]
1335
1342
  environment:
1336
1343
  NODE_ENV: development
1337
- PORT: 3000
1344
+ PORT: 9747
1338
1345
  DATABASE_URL: postgresql://windy:\${POSTGRES_PASSWORD}@postgres:5432/windy
1339
1346
  WINDY_BOOTSTRAP_ADMIN_PASSWORD: \${WINDY_BOOTSTRAP_ADMIN_PASSWORD:?请在 .env 中设置管理员初始密码}
1340
1347
  WINDY_AUDIT_QUEUE_FILE: /app/data/audit/recovery-queue.json
1341
1348
  WINDY_CONTAINERIZED: "1"
1349
+ BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD: "true"
1342
1350
  WINDY_SYSTEM_USAGE_PATHS: /app/data/audit
1343
1351
  ports:
1344
- - "3000:3000"
1352
+ - "9747:9747"
1345
1353
  volumes:
1346
1354
  - ./apps/server:/app/apps/server
1347
1355
  - ./packages:/app/packages
@@ -1357,14 +1365,26 @@ var starterCompose = `services:
1357
1365
  command: ["bun", "run", "--cwd", "apps/web", "dev"]
1358
1366
  environment:
1359
1367
  NODE_ENV: development
1360
- WINDY_DEV_PORT: 9746
1361
- API_UPSTREAM: http://server:3000
1368
+ WINDY_DEV_PORT: 18746
1369
+ WINDY_DEV_USE_POLLING: "1"
1370
+ API_UPSTREAM: http://server:9747
1362
1371
  VITE_DEV_HOST: 0.0.0.0
1363
1372
  ports:
1364
- - "9746:9746"
1373
+ - "18746:18746"
1365
1374
  volumes:
1366
1375
  - ./apps/web:/app/apps/web
1367
1376
  - ./packages:/app/packages
1377
+ healthcheck:
1378
+ test:
1379
+ [
1380
+ "CMD",
1381
+ "bun",
1382
+ "-e",
1383
+ "const r=await fetch('http://127.0.0.1:18746/-/windy-dev');process.exit(r.ok?0:1)",
1384
+ ]
1385
+ interval: 5s
1386
+ timeout: 3s
1387
+ retries: 6
1368
1388
  depends_on:
1369
1389
  - server
1370
1390
 
@@ -1435,15 +1455,17 @@ create-windy 已创建仅供本机使用且被 Git 忽略的 \`.env\`,其中
1435
1455
  文件权限为 \`0600\`。\`bun run dev\` 会通过 Docker Compose 等待 PostgreSQL、执行
1436
1456
  Drizzle migration,再启动 Server 与 Web;不要跳过 migration 后直接把 Server 连接到
1437
1457
  空数据库。首次启动需要本机已经安装并启动 Docker 与 Docker Compose,并可能需要下载
1438
- 基础镜像。
1458
+ 基础镜像。后续 \`bun run dev\` 会复用已有镜像,不会重复构建;依赖、Dockerfile 或
1459
+ \`bun.lock\` 变化后运行 \`bun run dev:build\`。
1439
1460
 
1440
1461
  | 服务 | 地址 |
1441
1462
  | ---- | ---- |
1442
- | Web | http://localhost:9746 |
1443
- | Server 健康检查 | http://localhost:3000/api/health |
1444
- | PostgreSQL | localhost:5432 |
1463
+ | Web | http://localhost:18746 |
1464
+ | Server 健康检查 | http://localhost:9747/api/health |
1465
+ | PostgreSQL | localhost:15432 |
1445
1466
 
1446
- Docker Web 固定使用 \`9746\`。只启动单个应用时使用:
1467
+ Docker Web 固定使用 \`18746\`,Web 与 Server 都挂载宿主机源码并保持热更新。只启动
1468
+ 单个应用时使用:
1447
1469
 
1448
1470
  \`\`\`bash
1449
1471
  bun run dev:web
@@ -1502,6 +1524,7 @@ ${moduleRows}
1502
1524
 
1503
1525
  \`\`\`bash
1504
1526
  bun run dev
1527
+ bun run dev:build
1505
1528
  bun run dev:web
1506
1529
  bun run dev:server
1507
1530
  bun run typecheck
@@ -1574,7 +1597,8 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
1574
1597
  ...includeOxc ? {
1575
1598
  prepare: "git rev-parse --git-dir >/dev/null 2>&1 || exit 0; husky"
1576
1599
  } : {},
1577
- dev: "docker compose up --build",
1600
+ dev: "docker compose up",
1601
+ "dev:build": "docker compose up --build",
1578
1602
  ...generatorVersion ? { windy: "create-windy" } : {},
1579
1603
  "dev:web": "bun run --cwd apps/web dev",
1580
1604
  "dev:server": "bun run --cwd apps/server dev",
@@ -1585,8 +1609,7 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
1585
1609
  typecheck: [
1586
1610
  "bun run --cwd apps/web typecheck",
1587
1611
  "bun run --cwd apps/server typecheck",
1588
- "bun run --cwd packages/agent-contracts typecheck",
1589
- "bun run --cwd packages/agent-client typecheck",
1612
+ "bun run --cwd packages/ai-client typecheck",
1590
1613
  "bun run --cwd packages/jobs typecheck",
1591
1614
  "bun run --cwd packages/server-sdk typecheck",
1592
1615
  "bun run --cwd packages/storage typecheck",
@@ -1625,8 +1648,7 @@ var templateDirectories = [
1625
1648
  "apps/server",
1626
1649
  "apps/web",
1627
1650
  "examples",
1628
- "packages/agent-client",
1629
- "packages/agent-contracts",
1651
+ "packages/ai-client",
1630
1652
  "packages/config",
1631
1653
  "packages/crud-generator",
1632
1654
  "packages/database",
@@ -3790,7 +3812,7 @@ function sentenceWithoutStop(value) {
3790
3812
 
3791
3813
  // src/update/cli.ts
3792
3814
  import { rm as rm8 } from "node:fs/promises";
3793
- import { resolve as resolve4 } from "node:path";
3815
+ import { resolve as resolve5 } from "node:path";
3794
3816
 
3795
3817
  // src/update/doctor.ts
3796
3818
  import { readFile as readFile11 } from "node:fs/promises";
@@ -3828,9 +3850,10 @@ import {
3828
3850
  mkdir as mkdir5,
3829
3851
  readFile as readFile10,
3830
3852
  rm as rm5,
3853
+ rmdir,
3831
3854
  writeFile as writeFile9
3832
3855
  } from "node:fs/promises";
3833
- import { dirname as dirname3, join as join10 } from "node:path";
3856
+ import { dirname as dirname3, join as join10, resolve as resolve4, sep as sep3 } from "node:path";
3834
3857
  var updateStatePath = ".windy/update-state.json";
3835
3858
  async function applyTransaction(plan) {
3836
3859
  if (plan.conflicts.length) {
@@ -3868,7 +3891,11 @@ async function applyTransaction(plan) {
3868
3891
  for (const file of changed) {
3869
3892
  const target = join10(plan.projectDirectory, file.path);
3870
3893
  if (file.action === "delete") {
3894
+ const existed = await exists(target);
3871
3895
  await rm5(target, { force: true });
3896
+ if (existed) {
3897
+ await removeEmptyDeletedFileParents(target, plan.projectDirectory);
3898
+ }
3872
3899
  continue;
3873
3900
  }
3874
3901
  if (!file.content)
@@ -3931,6 +3958,26 @@ async function exists(path2) {
3931
3958
  return false;
3932
3959
  }
3933
3960
  }
3961
+ async function removeEmptyDeletedFileParents(deletedFile, projectDirectory) {
3962
+ const projectRoot = resolve4(projectDirectory);
3963
+ let directory = dirname3(resolve4(deletedFile));
3964
+ while (directory !== projectRoot && directory.startsWith(`${projectRoot}${sep3}`)) {
3965
+ try {
3966
+ await rmdir(directory);
3967
+ } catch (error) {
3968
+ if (isDirectoryNotRemovable(error))
3969
+ return;
3970
+ throw error;
3971
+ }
3972
+ directory = dirname3(directory);
3973
+ }
3974
+ }
3975
+ function isDirectoryNotRemovable(error) {
3976
+ if (!(error instanceof Error) || !("code" in error))
3977
+ return false;
3978
+ const code = error.code;
3979
+ return code === "ENOENT" || code === "ENOTEMPTY" || code === "EEXIST";
3980
+ }
3934
3981
  function assertSafePath(path2) {
3935
3982
  if (!path2 || path2.startsWith("/") || path2.split("/").includes("..")) {
3936
3983
  throw new Error(`更新计划包含不安全路径:${path2}`);
@@ -4014,7 +4061,9 @@ var recipes = [
4014
4061
  { id: "starter-0.2.12-to-0.2.13", from: "0.2.12", to: "0.2.13" },
4015
4062
  { id: "starter-0.2.13-to-0.2.14", from: "0.2.13", to: "0.2.14" },
4016
4063
  { id: "starter-0.2.14-to-0.2.15", from: "0.2.14", to: "0.2.15" },
4017
- { id: "starter-0.2.15-to-0.2.16", from: "0.2.15", to: "0.2.16" }
4064
+ { id: "starter-0.2.15-to-0.2.16", from: "0.2.15", to: "0.2.16" },
4065
+ { id: "starter-0.2.16-to-0.2.17", from: "0.2.16", to: "0.2.17" },
4066
+ { id: "starter-0.2.17-to-0.2.18", from: "0.2.17", to: "0.2.18" }
4018
4067
  ];
4019
4068
  function resolveRecipeChain(sourceVersion, targetVersion) {
4020
4069
  if (sourceVersion === targetVersion)
@@ -4483,7 +4532,7 @@ async function runLifecycleCommand(args) {
4483
4532
  const command = args[0];
4484
4533
  if (command !== "doctor" && command !== "update")
4485
4534
  return false;
4486
- const projectDirectory = resolve4(readValue2(args, "--cwd") || process.cwd());
4535
+ const projectDirectory = resolve5(readValue2(args, "--cwd") || process.cwd());
4487
4536
  if (command === "doctor") {
4488
4537
  const report = await diagnoseProject(projectDirectory, args.includes("--repair"));
4489
4538
  printDoctor(report);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,10 @@
1
1
  **/dist
2
2
  **/node_modules
3
+ **/.mypy_cache
4
+ **/.pytest_cache
5
+ **/.ruff_cache
6
+ **/.venv
7
+ **/__pycache__
3
8
  .git
4
9
  .gitignore
5
10
  .husky
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "a33bfe5d85129939050e231d5e5ab52a763041ac",
4
- "generatorVersion": "0.2.16",
3
+ "templateCommit": "1d4ba94edf509d57d0e3bd9ca36902b4e3778526",
4
+ "generatorVersion": "0.2.18",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -31,9 +31,19 @@
31
31
  - 这是 Starter 的初始配置;项目所有者可按团队需要保留、替换或扩展。
32
32
  - 实际可用命令和提交检查以当前项目的 `package.json` 与 Git hooks 为准。
33
33
 
34
+ ## Windy 升级边界
35
+
36
+ - 业务需求优先通过自有模块和既有扩展点实现,尽量不要直接修改 Windy 提供的平台核心、
37
+ Composition Root、共享脚手架和根工具链配置,以减少后续升级冲突。
38
+ - 修改前先查看 `.windy/files.json` 的 ownership;优先扩展 `project-owned` 路径,
39
+ 对 `windy-managed` 路径仅在没有可用扩展点时做最小改动。
40
+ - 确需修改 Windy 受管代码时,使用独立小提交记录原因和影响;升级前先运行
41
+ `bun run windy update --dry-run`,根据三方合并计划处理冲突。
42
+
34
43
  ## 常用入口
35
44
 
36
- - `bun run dev`:通过 Docker Compose 启动 PostgreSQL、自动 migration、Server Web。
45
+ - `bun run dev`:复用 Docker 镜像启动 PostgreSQL、自动 migration、Server 与支持热更新的 Web。
46
+ - `bun run dev:build`:依赖、Dockerfile 或 lockfile 变化后重建并启动开发环境。
37
47
  - `bun run dev:web`:启动 Web 开发服务。
38
48
  - `bun run dev:server`:启动 Server 开发服务。
39
49
  - `bun run lint`、`bun run typecheck`、`bun run test`、`bun run build`:执行基础验证;未选择 Oxc 时没有 lint 脚本。
@@ -30,15 +30,17 @@ create-windy 已创建仅供本机使用且被 Git 忽略的 `.env`,其中两
30
30
  文件权限为 `0600`。`bun run dev` 会通过 Docker Compose 等待 PostgreSQL、执行
31
31
  Drizzle migration,再启动 Server 与 Web;不要跳过 migration 后直接把 Server 连接到
32
32
  空数据库。首次启动需要本机已经安装并启动 Docker 与 Docker Compose,并可能需要下载
33
- 基础镜像。
33
+ 基础镜像。后续 `bun run dev` 会复用已有镜像,不会重复构建;依赖、Dockerfile 或
34
+ `bun.lock` 变化后运行 `bun run dev:build`。
34
35
 
35
36
  | 服务 | 地址 |
36
37
  | ---- | ---- |
37
- | Web | http://localhost:9746 |
38
- | Server 健康检查 | http://localhost:3000/api/health |
39
- | PostgreSQL | localhost:5432 |
38
+ | Web | http://localhost:18746 |
39
+ | Server 健康检查 | http://localhost:9747/api/health |
40
+ | PostgreSQL | localhost:15432 |
40
41
 
41
- Docker Web 固定使用 `9746`。只启动单个应用时使用:
42
+ Docker Web 固定使用 `18746`,Web 与 Server 都挂载宿主机源码并保持热更新。只启动
43
+ 单个应用时使用:
42
44
 
43
45
  ```bash
44
46
  bun run dev:web
@@ -112,6 +114,7 @@ create-windy 在本机 `.env` 中随机生成的 `WINDY_BOOTSTRAP_ADMIN_PASSWORD
112
114
 
113
115
  ```bash
114
116
  bun run dev
117
+ bun run dev:build
115
118
  bun run dev:web
116
119
  bun run dev:server
117
120
  bun run typecheck
@@ -2,6 +2,11 @@
2
2
  .DS_Store
3
3
  node_modules/
4
4
  dist/
5
+ .mypy_cache/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .venv/
9
+ __pycache__/
5
10
  packages/create-windy/template/
6
11
  .tmp/
7
12
  .windy/backups/
@@ -8,8 +8,7 @@ COPY apps/server/package.json apps/server/package.json
8
8
  COPY apps/web/package.json apps/web/package.json
9
9
  COPY apps/license/package.json apps/license/package.json
10
10
  COPY apps/signing-service/package.json apps/signing-service/package.json
11
- COPY packages/agent-client/package.json packages/agent-client/package.json
12
- COPY packages/agent-contracts/package.json packages/agent-contracts/package.json
11
+ COPY packages/ai-client/package.json packages/ai-client/package.json
13
12
  COPY packages/config/package.json packages/config/package.json
14
13
  COPY packages/create-windy/package.json packages/create-windy/package.json
15
14
  COPY packages/crud-generator/package.json packages/crud-generator/package.json
@@ -30,9 +29,9 @@ COPY packages packages
30
29
  COPY --chmod=0555 deploy/production/server-entrypoint.sh /usr/local/bin/windy-server-entrypoint
31
30
  RUN mkdir -p /app/data/audit /app/data/license && chown -R bun:bun /app/data
32
31
  ENV NODE_ENV=production
33
- ENV PORT=3000
32
+ ENV PORT=9747
34
33
  ENV WINDY_CONTAINERIZED=1
35
- EXPOSE 3000
34
+ EXPOSE 9747
36
35
  USER bun
37
- HEALTHCHECK --interval=15s --timeout=3s --start-period=15s --retries=4 CMD ["bun", "-e", "const r=await fetch('http://127.0.0.1:3000/api/ready');process.exit(r.ok?0:1)"]
36
+ HEALTHCHECK --interval=15s --timeout=3s --start-period=15s --retries=4 CMD ["bun", "-e", "const r=await fetch('http://127.0.0.1:9747/api/ready');process.exit(r.ok?0:1)"]
38
37
  CMD ["bun", "run", "--cwd", "apps/server", "start"]
@@ -53,8 +53,7 @@ import { registerSchedulerRoutes } from "./scheduler/routes.js";
53
53
  import { SchedulerWorker } from "./scheduler/worker.js";
54
54
  // @windy-module system.scheduler end
55
55
  // @windy-module system.settings begin
56
- import { registerBrandingSettingsRoutes } from "./settings/routes.js";
57
- import { createBrandingSettingsRepository } from "./settings/runtime.js";
56
+ import { createPlatformSettingsRuntime } from "./settings/runtime.js";
58
57
  // @windy-module system.settings end
59
58
  import { registerProfileRoutes } from "./profile/routes.js";
60
59
  // @windy-module system.notification begin
@@ -191,10 +190,10 @@ const storageRuntime = createStorageRuntime(
191
190
  );
192
191
  // @windy-module system.storage end
193
192
  // @windy-module system.settings begin
194
- const brandingSettingsRepository = createBrandingSettingsRepository(
195
- persistence?.db,
196
- { softwareName: platformConfig.name, logoUrl: platformConfig.logoUrl },
197
- );
193
+ const platformSettings = createPlatformSettingsRuntime(persistence?.db, {
194
+ softwareName: platformConfig.name,
195
+ logoUrl: platformConfig.logoUrl,
196
+ });
198
197
  // @windy-module system.settings end
199
198
  // @windy-module work-order begin
200
199
  const exampleModuleRuntime = createExampleModuleRuntime({
@@ -276,12 +275,7 @@ registerPlatformLicenseRoutes(app, platformLicenseRuntime);
276
275
  // @windy-module system.license end
277
276
 
278
277
  // @windy-module system.settings begin
279
- registerBrandingSettingsRoutes(
280
- app,
281
- brandingSettingsRepository,
282
- runtime,
283
- foundationSnapshot.features,
284
- );
278
+ platformSettings.register(app, runtime, foundationSnapshot.features);
285
279
  // @windy-module system.settings end
286
280
  // @windy-module system.notification begin
287
281
  registerNotificationRoutes(
@@ -304,7 +298,7 @@ registerStorageRoutes(
304
298
  uploads: storageRuntime.uploads,
305
299
  files: storageRuntime.files,
306
300
  isPublicBrandingFile: async (fileId) =>
307
- (await brandingSettingsRepository.get()).logoUrl ===
301
+ (await platformSettings.branding.get()).logoUrl ===
308
302
  `/api/platform/files/${fileId}`,
309
303
  },
310
304
  runtime,
@@ -387,7 +381,7 @@ validateServerModuleComposition({
387
381
  // @windy-module system.search end
388
382
  });
389
383
 
390
- const port = process.env.PORT ? Number(process.env.PORT) : 3000;
384
+ const port = process.env.PORT ? Number(process.env.PORT) : 9_747;
391
385
  app.listen(port);
392
386
  // @windy-module system.scheduler begin
393
387
  schedulerWorker.start();
@@ -0,0 +1,57 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { resolve } from "node:path";
3
+ import { DEFAULT_PLATFORM_WATERMARK_POLICY } from "@southwind-ai/shared";
4
+ import { drizzle } from "drizzle-orm/node-postgres";
5
+ import { migrate } from "drizzle-orm/node-postgres/migrator";
6
+ import { Pool } from "pg";
7
+ import { DrizzleWatermarkPolicyRepository } from "./drizzle-watermark-repository.js";
8
+
9
+ const databaseUrl = process.env.SETTINGS_TEST_DATABASE_URL;
10
+ const describePostgres = databaseUrl ? describe : describe.skip;
11
+
12
+ describePostgres("PostgreSQL watermark policy repository", () => {
13
+ let pool: Pool;
14
+
15
+ beforeAll(async () => {
16
+ pool = new Pool({ connectionString: databaseUrl });
17
+ const db = drizzle(pool);
18
+ await migrate(db, {
19
+ migrationsFolder: resolve(
20
+ import.meta.dir,
21
+ "../../../../packages/database/drizzle",
22
+ ),
23
+ migrationsSchema: "public",
24
+ migrationsTable: "__settings_test_migrations",
25
+ });
26
+ await pool.query("truncate table platform_watermark_policies");
27
+ });
28
+
29
+ afterAll(async () => {
30
+ await pool?.end();
31
+ });
32
+
33
+ test("保存后可由重建的 Repository 读取同一全局策略", async () => {
34
+ const repository = new DrizzleWatermarkPolicyRepository(
35
+ drizzle(pool),
36
+ DEFAULT_PLATFORM_WATERMARK_POLICY,
37
+ );
38
+ await repository.update(
39
+ {
40
+ enabled: true,
41
+ businessPagesEnabled: true,
42
+ customContent: "内部资料",
43
+ },
44
+ "user_settings",
45
+ );
46
+
47
+ const restarted = new DrizzleWatermarkPolicyRepository(
48
+ drizzle(pool),
49
+ DEFAULT_PLATFORM_WATERMARK_POLICY,
50
+ );
51
+ expect(await restarted.get()).toEqual({
52
+ enabled: true,
53
+ businessPagesEnabled: true,
54
+ customContent: "内部资料",
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,62 @@
1
+ import { platformWatermarkPolicies } from "@southwind-ai/database";
2
+ import type { PlatformWatermarkPolicy } from "@southwind-ai/shared";
3
+ import { eq } from "drizzle-orm";
4
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
5
+ import type { WatermarkPolicyRepository } from "./watermark-repository.js";
6
+
7
+ const PLATFORM_WATERMARK_ID = "platform";
8
+
9
+ export class DrizzleWatermarkPolicyRepository implements WatermarkPolicyRepository {
10
+ constructor(
11
+ private readonly db: NodePgDatabase,
12
+ private readonly defaults: PlatformWatermarkPolicy,
13
+ ) {}
14
+
15
+ async get(): Promise<PlatformWatermarkPolicy> {
16
+ const [row] = await this.db
17
+ .select()
18
+ .from(platformWatermarkPolicies)
19
+ .where(eq(platformWatermarkPolicies.id, PLATFORM_WATERMARK_ID))
20
+ .limit(1);
21
+ return row ? toPolicy(row) : { ...this.defaults };
22
+ }
23
+
24
+ async update(
25
+ policy: PlatformWatermarkPolicy,
26
+ actorId: string,
27
+ ): Promise<PlatformWatermarkPolicy> {
28
+ const [row] = await this.db
29
+ .insert(platformWatermarkPolicies)
30
+ .values({
31
+ id: PLATFORM_WATERMARK_ID,
32
+ ...policy,
33
+ createdBy: actorId,
34
+ updatedBy: actorId,
35
+ })
36
+ .onConflictDoUpdate({
37
+ target: platformWatermarkPolicies.id,
38
+ set: {
39
+ enabled: policy.enabled,
40
+ businessPagesEnabled: policy.businessPagesEnabled,
41
+ customContent: policy.customContent,
42
+ updatedAt: new Date(),
43
+ updatedBy: actorId,
44
+ },
45
+ })
46
+ .returning();
47
+ if (!row) throw new Error("水印策略保存失败");
48
+ return toPolicy(row);
49
+ }
50
+ }
51
+
52
+ function toPolicy(row: {
53
+ enabled: boolean;
54
+ businessPagesEnabled: boolean;
55
+ customContent: string;
56
+ }): PlatformWatermarkPolicy {
57
+ return {
58
+ enabled: row.enabled,
59
+ businessPagesEnabled: row.businessPagesEnabled,
60
+ customContent: row.customContent,
61
+ };
62
+ }
@@ -1,13 +1,40 @@
1
- import type { PlatformBrandingSettings } from "@southwind-ai/shared";
1
+ import type {
2
+ FeatureFlagDefinition,
3
+ PlatformBrandingSettings,
4
+ } from "@southwind-ai/shared";
5
+ import { DEFAULT_PLATFORM_WATERMARK_POLICY } from "@southwind-ai/shared";
2
6
  import type { NodePgDatabase } from "drizzle-orm/node-postgres";
7
+ import type { createServerRuntime } from "../runtime.js";
8
+ import type { RouteApp } from "../system/route-types.js";
3
9
  import { DrizzleBrandingSettingsRepository } from "./drizzle-repository.js";
10
+ import { DrizzleWatermarkPolicyRepository } from "./drizzle-watermark-repository.js";
4
11
  import { InMemoryBrandingSettingsRepository } from "./repository.js";
12
+ import { registerBrandingSettingsRoutes } from "./routes.js";
13
+ import { InMemoryWatermarkPolicyRepository } from "./watermark-repository.js";
14
+ import { registerWatermarkPolicyRoutes } from "./watermark-routes.js";
5
15
 
6
- export function createBrandingSettingsRepository(
16
+ export function createPlatformSettingsRuntime(
7
17
  db: NodePgDatabase | undefined,
8
18
  defaults: PlatformBrandingSettings,
9
19
  ) {
10
- return db
20
+ const branding = db
11
21
  ? new DrizzleBrandingSettingsRepository(db, defaults)
12
22
  : new InMemoryBrandingSettingsRepository(defaults);
23
+ const watermark = db
24
+ ? new DrizzleWatermarkPolicyRepository(
25
+ db,
26
+ DEFAULT_PLATFORM_WATERMARK_POLICY,
27
+ )
28
+ : new InMemoryWatermarkPolicyRepository();
29
+ return {
30
+ branding,
31
+ register(
32
+ app: RouteApp,
33
+ server: ReturnType<typeof createServerRuntime>,
34
+ features: FeatureFlagDefinition[],
35
+ ) {
36
+ registerBrandingSettingsRoutes(app, branding, server, features);
37
+ registerWatermarkPolicyRoutes(app, watermark, server, features);
38
+ },
39
+ };
13
40
  }
@@ -0,0 +1,32 @@
1
+ import {
2
+ DEFAULT_PLATFORM_WATERMARK_POLICY,
3
+ type PlatformWatermarkPolicy,
4
+ } from "@southwind-ai/shared";
5
+
6
+ export interface WatermarkPolicyRepository {
7
+ get(): Promise<PlatformWatermarkPolicy>;
8
+ update(
9
+ policy: PlatformWatermarkPolicy,
10
+ actorId: string,
11
+ ): Promise<PlatformWatermarkPolicy>;
12
+ }
13
+
14
+ export class InMemoryWatermarkPolicyRepository implements WatermarkPolicyRepository {
15
+ private policy: PlatformWatermarkPolicy;
16
+
17
+ constructor(defaults = DEFAULT_PLATFORM_WATERMARK_POLICY) {
18
+ this.policy = { ...defaults };
19
+ }
20
+
21
+ async get(): Promise<PlatformWatermarkPolicy> {
22
+ return { ...this.policy };
23
+ }
24
+
25
+ async update(
26
+ policy: PlatformWatermarkPolicy,
27
+ _actorId: string,
28
+ ): Promise<PlatformWatermarkPolicy> {
29
+ this.policy = { ...policy };
30
+ return this.get();
31
+ }
32
+ }