create-windy 0.2.19 → 0.2.21
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 +5 -1
- package/dist/cli.js +392 -160
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +1 -0
- package/template/apps/agent-server/Dockerfile +20 -0
- package/template/apps/agent-server/README.md +46 -0
- package/template/apps/agent-server/pyproject.toml +44 -0
- package/template/apps/agent-server/src/southwind_agent_server/__init__.py +5 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +39 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +263 -0
- package/template/apps/agent-server/src/southwind_agent_server/config.py +141 -0
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +99 -0
- package/template/apps/agent-server/src/southwind_agent_server/deterministic.py +52 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +85 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +197 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +246 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +180 -0
- package/template/apps/agent-server/src/southwind_agent_server/sqlite_store.py +317 -0
- package/template/apps/agent-server/src/southwind_agent_server/sse.py +41 -0
- package/template/apps/agent-server/src/southwind_agent_server/store.py +144 -0
- package/template/apps/agent-server/src/southwind_agent_server/tool_host.py +80 -0
- package/template/apps/agent-server/tests/test_api.py +207 -0
- package/template/apps/agent-server/tests/test_config.py +56 -0
- package/template/apps/agent-server/tests/test_openai_provider.py +224 -0
- package/template/apps/agent-server/tests/test_sqlite_store.py +93 -0
- package/template/apps/agent-server/uv.lock +925 -0
- package/template/apps/server/src/agent/execution-grants.ts +89 -0
- package/template/apps/server/src/agent/remote-runtime.ts +128 -0
- package/template/apps/server/src/agent/run-contracts.ts +43 -0
- package/template/apps/server/src/agent/run-facade.test.ts +257 -0
- package/template/apps/server/src/agent/run-facade.ts +190 -0
- package/template/apps/server/src/agent/run-routes.ts +222 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +53 -0
- package/template/apps/server/src/agent/tool-host.test.ts +242 -0
- package/template/apps/server/src/agent/tool-host.ts +153 -0
- 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 -95
- 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 +54 -1
- package/template/apps/server/src/module-composition.ts +58 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +67 -0
- package/template/apps/server/src/module-host/initialize.test.ts +192 -0
- package/template/apps/server/src/module-host/initialize.ts +328 -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 +35 -113
- package/template/apps/server/src/module-runtime-validation.ts +40 -0
- 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/docker-compose.yml +25 -0
- package/template/docs/architecture/ai-runtime.md +744 -0
- package/template/docs/architecture/object-storage.md +12 -0
- package/template/docs/platform/agent-runtime.md +128 -0
- package/template/docs/platform/agent-tools.md +8 -1
- package/template/package.json +1 -0
- package/template/packages/config/index.test.ts +100 -0
- package/template/packages/config/index.ts +1 -0
- package/template/packages/config/package.json +2 -1
- package/template/packages/config/src/ai-openai-compatible.ts +131 -0
- 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 +53 -0
- package/template/packages/server-sdk/package.json +18 -3
- package/template/packages/server-sdk/src/actor.ts +15 -0
- package/template/packages/server-sdk/src/ai-operation-registration.ts +31 -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 +41 -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-host-port.ts +22 -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/dist/cli.js
CHANGED
|
@@ -228,10 +228,15 @@ var optionalModuleManifests = [
|
|
|
228
228
|
"system.audit"
|
|
229
229
|
]),
|
|
230
230
|
fileRoots: [
|
|
231
|
+
"apps/agent-server",
|
|
231
232
|
"apps/server/src/agent",
|
|
232
233
|
"packages/modules/src/system-agent-tools.ts"
|
|
233
234
|
],
|
|
234
|
-
documentation: [
|
|
235
|
+
documentation: [
|
|
236
|
+
"docs/architecture/ai-runtime.md",
|
|
237
|
+
"docs/platform/agent-runtime.md",
|
|
238
|
+
"docs/platform/agent-tools.md"
|
|
239
|
+
]
|
|
235
240
|
},
|
|
236
241
|
{
|
|
237
242
|
...module("system.scheduler", "任务调度", "任务、重试、历史和恢复。", [
|
|
@@ -727,7 +732,7 @@ var helpText = `create-windy <项目名> [选项]
|
|
|
727
732
|
`;
|
|
728
733
|
|
|
729
734
|
// src/create-project.ts
|
|
730
|
-
import { mkdir as mkdir4, readdir as
|
|
735
|
+
import { mkdir as mkdir4, readdir as readdir6, rm as rm4 } from "node:fs/promises";
|
|
731
736
|
|
|
732
737
|
// src/commands.ts
|
|
733
738
|
import { spawn } from "node:child_process";
|
|
@@ -881,7 +886,10 @@ async function initializeLocalEnvironment(projectRoot, generateSecret = defaultS
|
|
|
881
886
|
if (!gitignore.split(/\r?\n/).some((line) => line.trim() === ".env")) {
|
|
882
887
|
throw new Error("生成项目的 .gitignore 未排除 .env,拒绝写入本地密码");
|
|
883
888
|
}
|
|
884
|
-
|
|
889
|
+
let content = setEmptyVariable(setEmptyVariable(example, "POSTGRES_PASSWORD", generateSecret()), "WINDY_BOOTSTRAP_ADMIN_PASSWORD", generateSecret());
|
|
890
|
+
if (/^AGENT_SERVER_INTERNAL_TOKEN=[ \t\r]*$/m.test(content)) {
|
|
891
|
+
content = setEmptyVariable(content, "AGENT_SERVER_INTERNAL_TOKEN", generateSecret());
|
|
892
|
+
}
|
|
885
893
|
let handle;
|
|
886
894
|
try {
|
|
887
895
|
handle = await open(target, "wx", 384);
|
|
@@ -920,16 +928,147 @@ import {
|
|
|
920
928
|
copyFile,
|
|
921
929
|
lstat as lstat4,
|
|
922
930
|
mkdir as mkdir3,
|
|
923
|
-
readFile as
|
|
924
|
-
readdir as
|
|
931
|
+
readFile as readFile10,
|
|
932
|
+
readdir as readdir5,
|
|
925
933
|
rm as rm3,
|
|
926
|
-
writeFile as
|
|
934
|
+
writeFile as writeFile9
|
|
927
935
|
} from "node:fs/promises";
|
|
928
|
-
import { basename as basename3, extname as extname3, join as
|
|
936
|
+
import { basename as basename3, extname as extname3, join as join10 } from "node:path";
|
|
929
937
|
|
|
930
938
|
// src/customer-docker.ts
|
|
931
|
-
import { readFile as
|
|
939
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
940
|
+
import { join as join3 } from "node:path";
|
|
941
|
+
|
|
942
|
+
// src/docker-copy-sync.ts
|
|
943
|
+
import { readdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
932
944
|
import { join as join2 } from "node:path";
|
|
945
|
+
var webDockerfilePath = "apps/web/Dockerfile";
|
|
946
|
+
var webEntryDirectory = "apps/web";
|
|
947
|
+
var depsCopyAnchor = "windy-copy:deps";
|
|
948
|
+
var buildCopyAnchor = "windy-copy:build";
|
|
949
|
+
async function listWorkspacePackages(root) {
|
|
950
|
+
const manifest = await readManifest(join2(root, "package.json"));
|
|
951
|
+
const packages = [];
|
|
952
|
+
for (const pattern of manifest.workspaces ?? []) {
|
|
953
|
+
if (!pattern.endsWith("/*")) {
|
|
954
|
+
throw new Error(`暂不支持的 workspace pattern: ${pattern}`);
|
|
955
|
+
}
|
|
956
|
+
const base = pattern.slice(0, -2);
|
|
957
|
+
const entries = await readdir(join2(root, base), {
|
|
958
|
+
withFileTypes: true
|
|
959
|
+
}).catch((error) => {
|
|
960
|
+
if (isMissing(error))
|
|
961
|
+
return [];
|
|
962
|
+
throw error;
|
|
963
|
+
});
|
|
964
|
+
for (const entry of entries) {
|
|
965
|
+
if (!entry.isDirectory())
|
|
966
|
+
continue;
|
|
967
|
+
const directory = `${base}/${entry.name}`;
|
|
968
|
+
const manifestPath = join2(root, directory, "package.json");
|
|
969
|
+
if (!await Bun.file(manifestPath).exists())
|
|
970
|
+
continue;
|
|
971
|
+
const value = await readManifest(manifestPath);
|
|
972
|
+
packages.push({
|
|
973
|
+
name: value.name,
|
|
974
|
+
directory,
|
|
975
|
+
dependencies: [
|
|
976
|
+
...Object.keys(value.dependencies ?? {}),
|
|
977
|
+
...Object.keys(value.devDependencies ?? {})
|
|
978
|
+
]
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
return packages.sort((a, b) => a.directory.localeCompare(b.directory));
|
|
983
|
+
}
|
|
984
|
+
function workspaceDependencyClosure(packages, entryDirectory) {
|
|
985
|
+
const directoryByName = new Map;
|
|
986
|
+
const packageByDirectory = new Map;
|
|
987
|
+
for (const pkg of packages) {
|
|
988
|
+
if (pkg.name)
|
|
989
|
+
directoryByName.set(pkg.name, pkg.directory);
|
|
990
|
+
packageByDirectory.set(pkg.directory, pkg);
|
|
991
|
+
}
|
|
992
|
+
const visited = new Set([entryDirectory]);
|
|
993
|
+
const queue = [entryDirectory];
|
|
994
|
+
while (queue.length) {
|
|
995
|
+
const current = packageByDirectory.get(queue.shift());
|
|
996
|
+
if (!current)
|
|
997
|
+
continue;
|
|
998
|
+
for (const dependency of current.dependencies) {
|
|
999
|
+
const directory = directoryByName.get(dependency);
|
|
1000
|
+
if (directory && !visited.has(directory)) {
|
|
1001
|
+
visited.add(directory);
|
|
1002
|
+
queue.push(directory);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return [...visited].sort((a, b) => a.localeCompare(b));
|
|
1007
|
+
}
|
|
1008
|
+
function dockerCopyLines(directories) {
|
|
1009
|
+
return directories.map((directory) => `COPY ${directory} ${directory}`);
|
|
1010
|
+
}
|
|
1011
|
+
function dockerManifestCopyLines(directories) {
|
|
1012
|
+
return directories.map((directory) => `COPY ${directory}/package.json ${directory}/package.json`);
|
|
1013
|
+
}
|
|
1014
|
+
function rewriteAnchoredSection(source, anchor, lines) {
|
|
1015
|
+
const { begin, end } = locateAnchoredSection(source, anchor);
|
|
1016
|
+
const all = source.split(`
|
|
1017
|
+
`);
|
|
1018
|
+
return [...all.slice(0, begin + 1), ...lines, ...all.slice(end)].join(`
|
|
1019
|
+
`);
|
|
1020
|
+
}
|
|
1021
|
+
function hasCopyAnchors(source) {
|
|
1022
|
+
return source.includes(`# ${depsCopyAnchor} begin`) && source.includes(`# ${buildCopyAnchor} begin`);
|
|
1023
|
+
}
|
|
1024
|
+
function syncWebDockerfileSource(source, workspaceDirectories, buildDirectories) {
|
|
1025
|
+
const withDeps = rewriteAnchoredSection(source, depsCopyAnchor, dockerManifestCopyLines(workspaceDirectories));
|
|
1026
|
+
return rewriteAnchoredSection(withDeps, buildCopyAnchor, dockerCopyLines(buildDirectories));
|
|
1027
|
+
}
|
|
1028
|
+
async function syncWebDockerfileInProject(root, options = {}) {
|
|
1029
|
+
const path = join2(root, webDockerfilePath);
|
|
1030
|
+
let source;
|
|
1031
|
+
try {
|
|
1032
|
+
source = await readFile2(path, "utf8");
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (isMissing(error))
|
|
1035
|
+
return { synced: false, changed: false };
|
|
1036
|
+
throw error;
|
|
1037
|
+
}
|
|
1038
|
+
if (!hasCopyAnchors(source)) {
|
|
1039
|
+
if (!options.requireAnchors)
|
|
1040
|
+
return { synced: false, changed: false };
|
|
1041
|
+
throw new Error(`${webDockerfilePath} 缺少 # ${depsCopyAnchor} / # ${buildCopyAnchor} 锚点段,` + `请先按最新模板为 deps 与 build 阶段的 COPY 段添加锚点后再同步`);
|
|
1042
|
+
}
|
|
1043
|
+
const packages = await listWorkspacePackages(root);
|
|
1044
|
+
const next = syncWebDockerfileSource(source, packages.map(({ directory }) => directory), workspaceDependencyClosure(packages, webEntryDirectory));
|
|
1045
|
+
if (next !== source)
|
|
1046
|
+
await writeFile(path, next);
|
|
1047
|
+
return { synced: true, changed: next !== source };
|
|
1048
|
+
}
|
|
1049
|
+
function locateAnchoredSection(source, anchor) {
|
|
1050
|
+
const beginMarker = `# ${anchor} begin`;
|
|
1051
|
+
const endMarker = `# ${anchor} end`;
|
|
1052
|
+
const lines = source.split(`
|
|
1053
|
+
`);
|
|
1054
|
+
const begin = lines.indexOf(beginMarker);
|
|
1055
|
+
const end = lines.indexOf(endMarker);
|
|
1056
|
+
if (begin === -1 || end === -1 || end <= begin) {
|
|
1057
|
+
throw new Error(`Dockerfile 缺少锚点段 ${beginMarker} ... ${endMarker},请先迁移到锚点格式`);
|
|
1058
|
+
}
|
|
1059
|
+
if (lines.indexOf(beginMarker, begin + 1) !== -1 || lines.indexOf(endMarker, end + 1) !== -1) {
|
|
1060
|
+
throw new Error(`Dockerfile 锚点段 ${anchor} 重复,请保留唯一一段`);
|
|
1061
|
+
}
|
|
1062
|
+
return { begin, end };
|
|
1063
|
+
}
|
|
1064
|
+
async function readManifest(path) {
|
|
1065
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
1066
|
+
}
|
|
1067
|
+
function isMissing(error) {
|
|
1068
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/customer-docker.ts
|
|
933
1072
|
var supplierWorkspaceCopies = [
|
|
934
1073
|
"COPY apps/license/package.json apps/license/package.json",
|
|
935
1074
|
"COPY apps/signing-service/package.json apps/signing-service/package.json",
|
|
@@ -937,11 +1076,12 @@ var supplierWorkspaceCopies = [
|
|
|
937
1076
|
];
|
|
938
1077
|
var supplierWorkspacePaths = ["packages/license-sdk"];
|
|
939
1078
|
async function prepareCustomerDockerfiles(root) {
|
|
940
|
-
await Promise.all(["apps/server/Dockerfile",
|
|
941
|
-
const absolutePath =
|
|
942
|
-
const source = await
|
|
943
|
-
await
|
|
1079
|
+
await Promise.all(["apps/server/Dockerfile", webDockerfilePath].map(async (path) => {
|
|
1080
|
+
const absolutePath = join3(root, path);
|
|
1081
|
+
const source = await readFile3(absolutePath, "utf8");
|
|
1082
|
+
await writeFile2(absolutePath, rewriteCustomerDockerfile(source));
|
|
944
1083
|
}));
|
|
1084
|
+
await syncWebDockerfileInProject(root, { requireAnchors: true });
|
|
945
1085
|
}
|
|
946
1086
|
function rewriteCustomerDockerfile(source) {
|
|
947
1087
|
const lines = source.split(`
|
|
@@ -953,8 +1093,8 @@ function rewriteCustomerDockerfile(source) {
|
|
|
953
1093
|
}
|
|
954
1094
|
|
|
955
1095
|
// src/code-quality.ts
|
|
956
|
-
import { readFile as
|
|
957
|
-
import { join as
|
|
1096
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
1097
|
+
import { join as join4 } from "node:path";
|
|
958
1098
|
var oxcStarterPolicy = [
|
|
959
1099
|
"- 生成时默认预装 Oxlint、Oxfmt、lint-staged 与 Husky。",
|
|
960
1100
|
"- 提交前自动格式化暂存文件;lint、typecheck 和 test 保留为显式命令。",
|
|
@@ -963,30 +1103,30 @@ var oxcStarterPolicy = [
|
|
|
963
1103
|
`);
|
|
964
1104
|
var noCodeQualityPolicy = "- 本项目生成时未引入 lint 或 format 工具;项目所有者可按团队需要自行选择。";
|
|
965
1105
|
async function reflectCodeQualityChoice(targetDirectory, includeOxc) {
|
|
966
|
-
const path =
|
|
967
|
-
const source = await
|
|
1106
|
+
const path = join4(targetDirectory, "AGENTS.md");
|
|
1107
|
+
const source = await readFile4(path, "utf8");
|
|
968
1108
|
if (includeOxc)
|
|
969
1109
|
return;
|
|
970
1110
|
if (!source.includes(oxcStarterPolicy)) {
|
|
971
1111
|
throw new Error("无法同步 --no-oxc 的项目开发约束");
|
|
972
1112
|
}
|
|
973
|
-
await
|
|
1113
|
+
await writeFile3(path, source.replace(oxcStarterPolicy, noCodeQualityPolicy));
|
|
974
1114
|
}
|
|
975
1115
|
|
|
976
1116
|
// src/license-files.ts
|
|
977
|
-
import { mkdir, readFile as
|
|
978
|
-
import { dirname, join as
|
|
1117
|
+
import { mkdir, readFile as readFile5, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
1118
|
+
import { dirname, join as join5, resolve as resolve2 } from "node:path";
|
|
979
1119
|
import { fileURLToPath } from "node:url";
|
|
980
1120
|
var packageRoot = resolve2(dirname(fileURLToPath(import.meta.url)), "..");
|
|
981
1121
|
var apacheLicensePath = resolve2(packageRoot, "LICENSE");
|
|
982
1122
|
var windyLicensePath = ".windy/licenses/WINDY-APACHE-2.0.txt";
|
|
983
1123
|
async function applyWindyBaseLicense(targetDirectory) {
|
|
984
|
-
const target =
|
|
1124
|
+
const target = join5(targetDirectory, windyLicensePath);
|
|
985
1125
|
await mkdir(dirname(target), { recursive: true });
|
|
986
|
-
await
|
|
1126
|
+
await writeFile4(target, await readFile5(apacheLicensePath));
|
|
987
1127
|
}
|
|
988
1128
|
async function applyProjectLicense(targetDirectory, license, holder, year = new Date().getFullYear()) {
|
|
989
|
-
const target =
|
|
1129
|
+
const target = join5(targetDirectory, "LICENSE");
|
|
990
1130
|
if (license === "UNLICENSED") {
|
|
991
1131
|
await rm(target, { force: true });
|
|
992
1132
|
return;
|
|
@@ -996,11 +1136,11 @@ async function applyProjectLicense(targetDirectory, license, holder, year = new
|
|
|
996
1136
|
if (!owner)
|
|
997
1137
|
throw new Error("选择 MIT 时必须填写许可证权利人名称");
|
|
998
1138
|
await mkdir(targetDirectory, { recursive: true });
|
|
999
|
-
await
|
|
1139
|
+
await writeFile4(target, mitLicense(year, owner));
|
|
1000
1140
|
return;
|
|
1001
1141
|
}
|
|
1002
1142
|
await mkdir(targetDirectory, { recursive: true });
|
|
1003
|
-
await
|
|
1143
|
+
await writeFile4(target, await readFile5(apacheLicensePath));
|
|
1004
1144
|
}
|
|
1005
1145
|
function mitLicense(year, holder) {
|
|
1006
1146
|
return `MIT License
|
|
@@ -1029,8 +1169,8 @@ SOFTWARE.
|
|
|
1029
1169
|
|
|
1030
1170
|
// src/managed-files.ts
|
|
1031
1171
|
import { createHash } from "node:crypto";
|
|
1032
|
-
import { lstat, readdir, readFile as
|
|
1033
|
-
import { join as
|
|
1172
|
+
import { lstat, readdir as readdir2, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
1173
|
+
import { join as join6, relative, sep } from "node:path";
|
|
1034
1174
|
var managedFilesManifestPath = ".windy/files.json";
|
|
1035
1175
|
var ignoredDirectoryNames = new Set([".git", "node_modules", "dist"]);
|
|
1036
1176
|
var ignoredFiles = new Set([
|
|
@@ -1060,7 +1200,7 @@ var sharedScaffoldRoots = [
|
|
|
1060
1200
|
async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
|
|
1061
1201
|
const paths = await collectFiles(targetDirectory);
|
|
1062
1202
|
const files = await Promise.all(paths.map(async (path) => {
|
|
1063
|
-
const content = await
|
|
1203
|
+
const content = await readFile6(join6(targetDirectory, path));
|
|
1064
1204
|
return {
|
|
1065
1205
|
path,
|
|
1066
1206
|
ownership: classifyOwnership(path),
|
|
@@ -1073,11 +1213,11 @@ async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
|
|
|
1073
1213
|
algorithm: "sha256",
|
|
1074
1214
|
files
|
|
1075
1215
|
};
|
|
1076
|
-
await
|
|
1216
|
+
await writeFile5(join6(targetDirectory, managedFilesManifestPath), `${JSON.stringify(manifest, null, 2)}
|
|
1077
1217
|
`);
|
|
1078
1218
|
}
|
|
1079
1219
|
async function readManagedFilesManifest(targetDirectory) {
|
|
1080
|
-
const parsed = JSON.parse(await
|
|
1220
|
+
const parsed = JSON.parse(await readFile6(join6(targetDirectory, managedFilesManifestPath), "utf8"));
|
|
1081
1221
|
if (!isManagedFilesManifest(parsed)) {
|
|
1082
1222
|
throw new Error("受管文件清单无效,无法安全更新");
|
|
1083
1223
|
}
|
|
@@ -1098,10 +1238,10 @@ async function collectFiles(root) {
|
|
|
1098
1238
|
await visit(root, files);
|
|
1099
1239
|
return files.sort();
|
|
1100
1240
|
async function visit(directory, output) {
|
|
1101
|
-
for (const name of (await
|
|
1241
|
+
for (const name of (await readdir2(directory)).sort()) {
|
|
1102
1242
|
if (ignoredDirectoryNames.has(name))
|
|
1103
1243
|
continue;
|
|
1104
|
-
const path =
|
|
1244
|
+
const path = join6(directory, name);
|
|
1105
1245
|
const metadata = await lstat(path);
|
|
1106
1246
|
if (metadata.isSymbolicLink())
|
|
1107
1247
|
continue;
|
|
@@ -1135,8 +1275,8 @@ function isManagedFilesManifest(value) {
|
|
|
1135
1275
|
}
|
|
1136
1276
|
|
|
1137
1277
|
// src/module-materializer.ts
|
|
1138
|
-
import { lstat as lstat2, readFile as
|
|
1139
|
-
import { basename, extname, join as
|
|
1278
|
+
import { lstat as lstat2, readFile as readFile7, readdir as readdir3, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
|
|
1279
|
+
import { basename, extname, join as join7, relative as relative2, sep as sep2 } from "node:path";
|
|
1140
1280
|
var textExtensions = new Set([
|
|
1141
1281
|
".css",
|
|
1142
1282
|
".html",
|
|
@@ -1169,18 +1309,18 @@ async function materializeSelectedModules(targetDirectory, selectedNames) {
|
|
|
1169
1309
|
...manifest.documentation,
|
|
1170
1310
|
...manifest.schemaFiles
|
|
1171
1311
|
]);
|
|
1172
|
-
await Promise.all([...new Set(removedRoots)].map((path) => rm2(
|
|
1312
|
+
await Promise.all([...new Set(removedRoots)].map((path) => rm2(join7(targetDirectory, path), { recursive: true, force: true })));
|
|
1173
1313
|
await pruneWorkspaceReferences(targetDirectory, removedPackageNames, removedRoots);
|
|
1174
1314
|
}
|
|
1175
1315
|
async function stripModuleBlocks(root, selected) {
|
|
1176
1316
|
for (const path of await collectFiles2(root)) {
|
|
1177
1317
|
if (!textExtensions.has(extname(path)))
|
|
1178
1318
|
continue;
|
|
1179
|
-
const source = await
|
|
1319
|
+
const source = await readFile7(path, "utf8");
|
|
1180
1320
|
if (!source.includes("@windy-module"))
|
|
1181
1321
|
continue;
|
|
1182
1322
|
const output = stripTaggedSource(source, selected, relativePath(root, path));
|
|
1183
|
-
await
|
|
1323
|
+
await writeFile6(path, output);
|
|
1184
1324
|
}
|
|
1185
1325
|
}
|
|
1186
1326
|
function stripTaggedSource(source, selected, path = "source") {
|
|
@@ -1213,11 +1353,11 @@ async function readRemovedPackageNames(root, roots) {
|
|
|
1213
1353
|
const names = new Set;
|
|
1214
1354
|
for (const packageRoot2 of roots) {
|
|
1215
1355
|
try {
|
|
1216
|
-
const value = JSON.parse(await
|
|
1356
|
+
const value = JSON.parse(await readFile7(join7(root, packageRoot2, "package.json"), "utf8"));
|
|
1217
1357
|
if (value.name)
|
|
1218
1358
|
names.add(value.name);
|
|
1219
1359
|
} catch (error) {
|
|
1220
|
-
if (!
|
|
1360
|
+
if (!isMissing2(error))
|
|
1221
1361
|
throw error;
|
|
1222
1362
|
}
|
|
1223
1363
|
}
|
|
@@ -1226,7 +1366,7 @@ async function readRemovedPackageNames(root, roots) {
|
|
|
1226
1366
|
async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
|
|
1227
1367
|
for (const path of await collectFiles2(root)) {
|
|
1228
1368
|
if (basename(path) === "package.json") {
|
|
1229
|
-
const value = JSON.parse(await
|
|
1369
|
+
const value = JSON.parse(await readFile7(path, "utf8"));
|
|
1230
1370
|
let changed = false;
|
|
1231
1371
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
1232
1372
|
const dependencies = value[section];
|
|
@@ -1238,45 +1378,45 @@ async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
|
|
|
1238
1378
|
}
|
|
1239
1379
|
}
|
|
1240
1380
|
if (changed)
|
|
1241
|
-
await
|
|
1381
|
+
await writeFile6(path, `${JSON.stringify(value, null, 2)}
|
|
1242
1382
|
`);
|
|
1243
1383
|
continue;
|
|
1244
1384
|
}
|
|
1245
1385
|
if (basename(path) !== "Dockerfile")
|
|
1246
1386
|
continue;
|
|
1247
|
-
const source = await
|
|
1387
|
+
const source = await readFile7(path, "utf8");
|
|
1248
1388
|
const output = source.split(`
|
|
1249
1389
|
`).filter((line) => !removedRoots.some((root2) => line.includes(root2))).join(`
|
|
1250
1390
|
`);
|
|
1251
1391
|
if (output !== source)
|
|
1252
|
-
await
|
|
1392
|
+
await writeFile6(path, output);
|
|
1253
1393
|
}
|
|
1254
1394
|
}
|
|
1255
1395
|
async function pruneDatabaseArtifacts(root, tableNames, statementTokens) {
|
|
1256
1396
|
if (!tableNames.length && !statementTokens.length)
|
|
1257
1397
|
return;
|
|
1258
1398
|
const tablePatterns = tableNames.map((name) => new RegExp(`(?:"${escapeRegExp(name)}"|\\b${escapeRegExp(name)}\\b)`));
|
|
1259
|
-
const drizzleRoot =
|
|
1399
|
+
const drizzleRoot = join7(root, "packages/database/drizzle");
|
|
1260
1400
|
try {
|
|
1261
1401
|
for (const path of await collectFiles2(drizzleRoot)) {
|
|
1262
1402
|
if (path.endsWith(".sql")) {
|
|
1263
|
-
const source = await
|
|
1403
|
+
const source = await readFile7(path, "utf8");
|
|
1264
1404
|
const statements = source.split("--> statement-breakpoint").map((statement) => statement.trim()).filter(Boolean).filter((statement) => !tablePatterns.some((pattern) => pattern.test(statement)) && !statementTokens.some((token) => statement.includes(token)));
|
|
1265
|
-
await
|
|
1405
|
+
await writeFile6(path, statements.length ? `${statements.join(`
|
|
1266
1406
|
--> statement-breakpoint
|
|
1267
1407
|
`)}
|
|
1268
1408
|
` : `-- 此 migration 的模块未被当前项目选择。
|
|
1269
1409
|
`);
|
|
1270
1410
|
} else if (path.endsWith("_snapshot.json")) {
|
|
1271
|
-
const snapshot = JSON.parse(await
|
|
1411
|
+
const snapshot = JSON.parse(await readFile7(path, "utf8"));
|
|
1272
1412
|
for (const name of tableNames)
|
|
1273
1413
|
delete snapshot.tables?.[`public.${name}`];
|
|
1274
|
-
await
|
|
1414
|
+
await writeFile6(path, `${JSON.stringify(snapshot, null, 2)}
|
|
1275
1415
|
`);
|
|
1276
1416
|
}
|
|
1277
1417
|
}
|
|
1278
1418
|
} catch (error) {
|
|
1279
|
-
if (!
|
|
1419
|
+
if (!isMissing2(error))
|
|
1280
1420
|
throw error;
|
|
1281
1421
|
}
|
|
1282
1422
|
}
|
|
@@ -1285,10 +1425,10 @@ async function collectFiles2(root) {
|
|
|
1285
1425
|
await visit(root);
|
|
1286
1426
|
return files.sort();
|
|
1287
1427
|
async function visit(directory) {
|
|
1288
|
-
for (const name of (await
|
|
1428
|
+
for (const name of (await readdir3(directory)).sort()) {
|
|
1289
1429
|
if ([".git", "node_modules", "dist"].includes(name))
|
|
1290
1430
|
continue;
|
|
1291
|
-
const path =
|
|
1431
|
+
const path = join7(directory, name);
|
|
1292
1432
|
const metadata = await lstat2(path);
|
|
1293
1433
|
if (metadata.isSymbolicLink())
|
|
1294
1434
|
continue;
|
|
@@ -1305,12 +1445,12 @@ function relativePath(root, path) {
|
|
|
1305
1445
|
function escapeRegExp(value) {
|
|
1306
1446
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1307
1447
|
}
|
|
1308
|
-
function
|
|
1448
|
+
function isMissing2(error) {
|
|
1309
1449
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1310
1450
|
}
|
|
1311
1451
|
|
|
1312
|
-
// src/
|
|
1313
|
-
var
|
|
1452
|
+
// src/runtime-profile-files.ts
|
|
1453
|
+
var starterComposeBase = `services:
|
|
1314
1454
|
postgres:
|
|
1315
1455
|
image: postgres:17-alpine
|
|
1316
1456
|
environment:
|
|
@@ -1354,6 +1494,10 @@ var starterCompose = `services:
|
|
|
1354
1494
|
WINDY_CONTAINERIZED: "1"
|
|
1355
1495
|
BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD: "true"
|
|
1356
1496
|
WINDY_SYSTEM_USAGE_PATHS: /app/data/audit
|
|
1497
|
+
PLATFORM_ENABLE_AI: \${PLATFORM_ENABLE_AI:-false}
|
|
1498
|
+
AGENT_SERVER_URL: http://agent-server:8080
|
|
1499
|
+
AGENT_TOOL_HOST_URL: http://server:9747
|
|
1500
|
+
AGENT_SERVER_INTERNAL_TOKEN: \${AGENT_SERVER_INTERNAL_TOKEN:-}
|
|
1357
1501
|
ports:
|
|
1358
1502
|
- "9747:9747"
|
|
1359
1503
|
volumes:
|
|
@@ -1397,11 +1541,50 @@ var starterCompose = `services:
|
|
|
1397
1541
|
volumes:
|
|
1398
1542
|
postgres-data:
|
|
1399
1543
|
server-audit-data:
|
|
1544
|
+
__AGENT_VOLUME__
|
|
1545
|
+
`;
|
|
1546
|
+
var agentComposeService = ` agent-server:
|
|
1547
|
+
profiles: ["agent"]
|
|
1548
|
+
build:
|
|
1549
|
+
context: .
|
|
1550
|
+
dockerfile: apps/agent-server/Dockerfile
|
|
1551
|
+
environment:
|
|
1552
|
+
AGENT_SERVER_INTERNAL_TOKEN: \${AGENT_SERVER_INTERNAL_TOKEN:-}
|
|
1553
|
+
AGENT_RUN_STORE_PATH: /app/data/agent/runs.sqlite3
|
|
1554
|
+
QWEN_API_URL: \${QWEN_API_URL:-}
|
|
1555
|
+
QWEN_API_KEY: \${QWEN_API_KEY:-}
|
|
1556
|
+
QWEN_MODEL: \${QWEN_MODEL:-}
|
|
1557
|
+
AI_PROVIDER_CONNECT_TIMEOUT_MS: \${AI_PROVIDER_CONNECT_TIMEOUT_MS:-5000}
|
|
1558
|
+
AI_PROVIDER_REQUEST_TIMEOUT_MS: \${AI_PROVIDER_REQUEST_TIMEOUT_MS:-60000}
|
|
1559
|
+
AI_PROVIDER_MAX_RESPONSE_BYTES: \${AI_PROVIDER_MAX_RESPONSE_BYTES:-2097152}
|
|
1560
|
+
volumes:
|
|
1561
|
+
- agent-run-data:/app/data/agent
|
|
1562
|
+
depends_on:
|
|
1563
|
+
server:
|
|
1564
|
+
condition: service_started
|
|
1565
|
+
|
|
1400
1566
|
`;
|
|
1401
|
-
function
|
|
1567
|
+
function starterComposeFor(includeAgent) {
|
|
1568
|
+
return starterComposeBase.replace(` web:
|
|
1569
|
+
`, `${includeAgent ? agentComposeService : ""} web:
|
|
1570
|
+
`).replace("__AGENT_VOLUME__", includeAgent ? " agent-run-data:" : "");
|
|
1571
|
+
}
|
|
1572
|
+
var starterCompose = starterComposeFor(true);
|
|
1573
|
+
function environmentExample(includeExample, includeAgent = true) {
|
|
1402
1574
|
return `# 复制为 .env 后填写。不要提交真实密码。
|
|
1403
1575
|
POSTGRES_PASSWORD=
|
|
1404
1576
|
WINDY_BOOTSTRAP_ADMIN_PASSWORD=
|
|
1577
|
+
${includeAgent ? `
|
|
1578
|
+
# Agent profile 仅由 Server 读取;不要使用 VITE_ 前缀。
|
|
1579
|
+
PLATFORM_ENABLE_AI=false
|
|
1580
|
+
AGENT_SERVER_INTERNAL_TOKEN=
|
|
1581
|
+
QWEN_API_URL=
|
|
1582
|
+
QWEN_API_KEY=
|
|
1583
|
+
QWEN_MODEL=
|
|
1584
|
+
# AI_PROVIDER_CONNECT_TIMEOUT_MS=5000
|
|
1585
|
+
# AI_PROVIDER_REQUEST_TIMEOUT_MS=60000
|
|
1586
|
+
# AI_PROVIDER_MAX_RESPONSE_BYTES=2097152
|
|
1587
|
+
` : ""}
|
|
1405
1588
|
|
|
1406
1589
|
# 可选搜索配置;不配置时启用 Manifest 中默认开启的 Provider。${includeExample ? `
|
|
1407
1590
|
# SEARCH_PROVIDER_KEYS=work-order.ticket.search` : `
|
|
@@ -1411,7 +1594,8 @@ WINDY_BOOTSTRAP_ADMIN_PASSWORD=
|
|
|
1411
1594
|
# SEARCH_MAX_REQUESTS_PER_MINUTE=30
|
|
1412
1595
|
`;
|
|
1413
1596
|
}
|
|
1414
|
-
|
|
1597
|
+
// src/project-files.ts
|
|
1598
|
+
function starterDocsReadme(includeOfflineLicense = true, includeAgent = true) {
|
|
1415
1599
|
return `# Windy Starter 文档
|
|
1416
1600
|
|
|
1417
1601
|
本目录只包含客户运行项目需要的平台架构、开发接入和运维可观测性文档。
|
|
@@ -1422,11 +1606,13 @@ License 签发中心、独立签名服务、Vault、发布治理、内部任务
|
|
|
1422
1606
|
- 平台开发:从 [系统 CRUD API](./platform/system-crud-api.md) 和 [CRUD 生成器](./development/crud-generator.md) 开始。
|
|
1423
1607
|
${includeOfflineLicense ? "- License 消费端:[离线激活与安装协议](./license/license-offline-activation-code.md);运行时能力由 npm 依赖 `@southwind-ai/license` 提供。" : ""}
|
|
1424
1608
|
- 运维:[可观测性](./operations/observability.md) 与 [运维总览](./platform/operations-overview.md)。
|
|
1609
|
+
${includeAgent ? "- Agent:[Agent Runtime 接入](./platform/agent-runtime.md) 与 [受控工具](./platform/agent-tools.md)。" : ""}
|
|
1425
1610
|
- 兼容:[RuoYi-Vue Adapter](./adapters/ruoyi-vue-compatibility.md)。
|
|
1426
1611
|
`;
|
|
1427
1612
|
}
|
|
1428
1613
|
function starterReadme(projectName, includeExample = true, includeOxc = true, projectLicense = "UNLICENSED", selectedModules = []) {
|
|
1429
1614
|
const selected = new Set(selectedModules);
|
|
1615
|
+
const includeAgent = selected.has("system.agent");
|
|
1430
1616
|
const moduleRows = moduleDistributionCatalog.map(({ name, description, selection }) => `| \`${name}\` | ${selected.has(name) ? "已包含" : "未包含"} | ${selection === "required" ? "H0" : "H2"} | ${description} |`).join(`
|
|
1431
1617
|
`);
|
|
1432
1618
|
return `# ${projectName}
|
|
@@ -1482,6 +1668,21 @@ bun run dev:server
|
|
|
1482
1668
|
直接输出已有地址,其它服务占用时会自动使用后续端口,最多检查 100 个。实际地址始终
|
|
1483
1669
|
以终端输出为准。\`dev:server\` 不会自动启动 PostgreSQL 或执行 migration;未配置
|
|
1484
1670
|
\`DATABASE_URL\` 时使用内存模式。
|
|
1671
|
+
${includeAgent ? `
|
|
1672
|
+
## OpenAI-compatible Agent profile
|
|
1673
|
+
|
|
1674
|
+
Agent Runtime 默认不启动。先在仅供本机使用的 \`.env\` 中设置
|
|
1675
|
+
\`PLATFORM_ENABLE_AI=true\`、\`AGENT_SERVER_INTERNAL_TOKEN\`、\`QWEN_API_URL\`、
|
|
1676
|
+
\`QWEN_API_KEY\` 与 \`QWEN_MODEL\`,再运行:
|
|
1677
|
+
|
|
1678
|
+
\`\`\`bash
|
|
1679
|
+
bun run dev:agent
|
|
1680
|
+
\`\`\`
|
|
1681
|
+
|
|
1682
|
+
公网 Provider URL 必须使用 HTTPS;本机、容器服务名和内网地址允许 HTTP。
|
|
1683
|
+
\`QWEN_API_KEY\` 只进入 Agent Server,不会进入 Web 环境或返回给客户端。运行与模块
|
|
1684
|
+
接入细节见 [Agent Runtime 接入](./docs/platform/agent-runtime.md)。
|
|
1685
|
+
` : ""}
|
|
1485
1686
|
|
|
1486
1687
|
## 页面与登录
|
|
1487
1688
|
|
|
@@ -1604,7 +1805,9 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
|
|
|
1604
1805
|
prepare: "git rev-parse --git-dir >/dev/null 2>&1 || exit 0; husky"
|
|
1605
1806
|
} : {},
|
|
1606
1807
|
dev: "docker compose up",
|
|
1607
|
-
"
|
|
1808
|
+
"docker:sync": "create-windy sync-docker",
|
|
1809
|
+
"dev:build": "bun run docker:sync && docker compose up --build",
|
|
1810
|
+
...selectedModules.includes("system.agent") ? { "dev:agent": "docker compose --profile agent up --build" } : {},
|
|
1608
1811
|
...generatorVersion ? { windy: "create-windy" } : {},
|
|
1609
1812
|
"dev:web": "bun run --cwd apps/web dev",
|
|
1610
1813
|
"dev:server": "bun run --cwd apps/server dev",
|
|
@@ -1652,6 +1855,7 @@ var templateRootFiles = [
|
|
|
1652
1855
|
];
|
|
1653
1856
|
var templateDirectories = [
|
|
1654
1857
|
"apps/server",
|
|
1858
|
+
"apps/agent-server",
|
|
1655
1859
|
"apps/web",
|
|
1656
1860
|
"examples",
|
|
1657
1861
|
"packages/ai-client",
|
|
@@ -1668,6 +1872,7 @@ var templateDirectories = [
|
|
|
1668
1872
|
var templateDocumentationFiles = [
|
|
1669
1873
|
"docs/adapters/ruoyi-vue-compatibility.md",
|
|
1670
1874
|
"docs/architecture/admin-surface-boundary.md",
|
|
1875
|
+
"docs/architecture/ai-runtime.md",
|
|
1671
1876
|
"docs/architecture/data-scope-query-enforcement.md",
|
|
1672
1877
|
"docs/architecture/durable-jobs.md",
|
|
1673
1878
|
"docs/architecture/minimal-approval-workflow.md",
|
|
@@ -1678,6 +1883,7 @@ var templateDocumentationFiles = [
|
|
|
1678
1883
|
"docs/license/license-offline-activation-code.md",
|
|
1679
1884
|
"docs/operations/observability.md",
|
|
1680
1885
|
"docs/platform/agent-tools.md",
|
|
1886
|
+
"docs/platform/agent-runtime.md",
|
|
1681
1887
|
"docs/platform/audit-reliability.md",
|
|
1682
1888
|
"docs/platform/bulk-data-jobs.md",
|
|
1683
1889
|
"docs/platform/data-access-control.md",
|
|
@@ -1725,21 +1931,21 @@ var excludedRuntimeFiles = new Set([
|
|
|
1725
1931
|
]);
|
|
1726
1932
|
|
|
1727
1933
|
// src/provenance.ts
|
|
1728
|
-
import { mkdir as mkdir2, readFile as
|
|
1729
|
-
import { join as
|
|
1934
|
+
import { mkdir as mkdir2, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
1935
|
+
import { join as join8 } from "node:path";
|
|
1730
1936
|
var templateMetadataFile = ".windy-template.json";
|
|
1731
1937
|
var generationMetadataFile = ".windy/generation.json";
|
|
1732
1938
|
async function readTemplateMetadata(sourceRoot) {
|
|
1733
|
-
const path =
|
|
1734
|
-
const parsed = JSON.parse(await
|
|
1939
|
+
const path = join8(sourceRoot, templateMetadataFile);
|
|
1940
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
1735
1941
|
if (!isTemplateMetadata(parsed)) {
|
|
1736
1942
|
throw new Error("create-windy 模板来源元数据无效");
|
|
1737
1943
|
}
|
|
1738
1944
|
return parsed;
|
|
1739
1945
|
}
|
|
1740
1946
|
async function readGenerationProvenance(targetDirectory) {
|
|
1741
|
-
const path =
|
|
1742
|
-
const parsed = JSON.parse(await
|
|
1947
|
+
const path = join8(targetDirectory, generationMetadataFile);
|
|
1948
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
1743
1949
|
if (!isGenerationProvenance(parsed)) {
|
|
1744
1950
|
throw new Error("当前目录不是受支持的 Windy 项目:Project Lock 无效");
|
|
1745
1951
|
}
|
|
@@ -1771,9 +1977,9 @@ async function writeGenerationProvenance(targetDirectory, metadata, profile, sel
|
|
|
1771
1977
|
includeOxc
|
|
1772
1978
|
}
|
|
1773
1979
|
};
|
|
1774
|
-
const path =
|
|
1775
|
-
await mkdir2(
|
|
1776
|
-
await
|
|
1980
|
+
const path = join8(targetDirectory, generationMetadataFile);
|
|
1981
|
+
await mkdir2(join8(path, ".."), { recursive: true });
|
|
1982
|
+
await writeFile7(path, `${JSON.stringify(provenance, null, 2)}
|
|
1777
1983
|
`);
|
|
1778
1984
|
}
|
|
1779
1985
|
function isTemplateMetadata(value) {
|
|
@@ -1798,8 +2004,8 @@ function isModuleVersion(value) {
|
|
|
1798
2004
|
}
|
|
1799
2005
|
|
|
1800
2006
|
// src/registry-dependencies.ts
|
|
1801
|
-
import { lstat as lstat3, readFile as
|
|
1802
|
-
import { basename as basename2, extname as extname2, join as
|
|
2007
|
+
import { lstat as lstat3, readFile as readFile9, readdir as readdir4, writeFile as writeFile8 } from "node:fs/promises";
|
|
2008
|
+
import { basename as basename2, extname as extname2, join as join9 } from "node:path";
|
|
1803
2009
|
var dependencySections = [
|
|
1804
2010
|
"dependencies",
|
|
1805
2011
|
"devDependencies",
|
|
@@ -1827,14 +2033,14 @@ async function materializeRegistryDependencies(root, dependencies) {
|
|
|
1827
2033
|
await rewritePackageJson(path, dependencies);
|
|
1828
2034
|
continue;
|
|
1829
2035
|
}
|
|
1830
|
-
const source = await
|
|
2036
|
+
const source = await readFile9(path, "utf8");
|
|
1831
2037
|
const output = dependencies.reduce((value, dependency) => value.replaceAll(dependency.sourceName, dependency.name), source);
|
|
1832
2038
|
if (output !== source)
|
|
1833
|
-
await
|
|
2039
|
+
await writeFile8(path, output);
|
|
1834
2040
|
}
|
|
1835
2041
|
}
|
|
1836
2042
|
async function rewritePackageJson(path, registryDependencies) {
|
|
1837
|
-
const value = JSON.parse(await
|
|
2043
|
+
const value = JSON.parse(await readFile9(path, "utf8"));
|
|
1838
2044
|
let changed = false;
|
|
1839
2045
|
for (const section of dependencySections) {
|
|
1840
2046
|
const dependencies = value[section];
|
|
@@ -1849,7 +2055,7 @@ async function rewritePackageJson(path, registryDependencies) {
|
|
|
1849
2055
|
}
|
|
1850
2056
|
}
|
|
1851
2057
|
if (changed)
|
|
1852
|
-
await
|
|
2058
|
+
await writeFile8(path, `${JSON.stringify(value, null, 2)}
|
|
1853
2059
|
`);
|
|
1854
2060
|
}
|
|
1855
2061
|
async function collectFiles3(root) {
|
|
@@ -1857,10 +2063,10 @@ async function collectFiles3(root) {
|
|
|
1857
2063
|
await visit(root);
|
|
1858
2064
|
return files.sort();
|
|
1859
2065
|
async function visit(directory) {
|
|
1860
|
-
for (const name of (await
|
|
2066
|
+
for (const name of (await readdir4(directory)).sort()) {
|
|
1861
2067
|
if ([".git", "node_modules", "dist"].includes(name))
|
|
1862
2068
|
continue;
|
|
1863
|
-
const path =
|
|
2069
|
+
const path = join9(directory, name);
|
|
1864
2070
|
const metadata = await lstat3(path);
|
|
1865
2071
|
if (metadata.isSymbolicLink())
|
|
1866
2072
|
continue;
|
|
@@ -1884,39 +2090,39 @@ async function generateProject(input) {
|
|
|
1884
2090
|
const selectedModules = input.selectedModules ?? templateMetadata.modules.filter(({ name }) => requestedIncludeExample || name !== "work-order").map(({ name }) => name);
|
|
1885
2091
|
const includeExample = selectedModules.includes("work-order");
|
|
1886
2092
|
for (const relativePath2 of generationTemplatePaths(true, includeOxc)) {
|
|
1887
|
-
await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2),
|
|
2093
|
+
await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join10(input.targetDirectory, relativePath2));
|
|
1888
2094
|
}
|
|
1889
2095
|
await materializeSelectedModules(input.targetDirectory, selectedModules);
|
|
1890
2096
|
await materializeRegistryDependencies(input.targetDirectory, selectedModuleManifests(selectedModules).flatMap(({ registryDependencies }) => registryDependencies));
|
|
1891
|
-
const packagePath =
|
|
1892
|
-
const packageJson = JSON.parse(await
|
|
1893
|
-
await
|
|
2097
|
+
const packagePath = join10(input.targetDirectory, "package.json");
|
|
2098
|
+
const packageJson = JSON.parse(await readFile10(packagePath, "utf8"));
|
|
2099
|
+
await writeFile9(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
|
|
1894
2100
|
`);
|
|
1895
|
-
await
|
|
1896
|
-
await
|
|
1897
|
-
await
|
|
1898
|
-
await
|
|
2101
|
+
await writeFile9(join10(input.targetDirectory, "docker-compose.yml"), starterComposeFor(selectedModules.includes("system.agent")));
|
|
2102
|
+
await writeFile9(join10(input.targetDirectory, ".env.example"), environmentExample(includeExample, selectedModules.includes("system.agent")));
|
|
2103
|
+
await writeFile9(join10(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
|
|
2104
|
+
await writeFile9(join10(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license"), selectedModules.includes("system.agent")));
|
|
1899
2105
|
await reflectCodeQualityChoice(input.targetDirectory, includeOxc);
|
|
1900
2106
|
await applyWindyBaseLicense(input.targetDirectory);
|
|
1901
2107
|
await applyProjectLicense(input.targetDirectory, projectLicense, input.licenseHolder);
|
|
1902
2108
|
await prepareCustomerDockerfiles(input.targetDirectory);
|
|
1903
2109
|
await writeGenerationProvenance(input.targetDirectory, templateMetadata, input.profile ?? starterProfile.key, selectedModules, includeOxc, projectLicense);
|
|
1904
|
-
await rm3(
|
|
2110
|
+
await rm3(join10(input.targetDirectory, "bun.lock"), { force: true });
|
|
1905
2111
|
await writeManagedFilesManifest(input.targetDirectory, templateMetadata.generatorVersion);
|
|
1906
2112
|
}
|
|
1907
2113
|
async function resolveTemplateSource(sourceRoot, relativePath2) {
|
|
1908
|
-
const source =
|
|
2114
|
+
const source = join10(sourceRoot, relativePath2);
|
|
1909
2115
|
try {
|
|
1910
2116
|
await lstat4(source);
|
|
1911
2117
|
return source;
|
|
1912
2118
|
} catch (error) {
|
|
1913
|
-
if (!
|
|
2119
|
+
if (!isMissing3(error))
|
|
1914
2120
|
throw error;
|
|
1915
2121
|
}
|
|
1916
2122
|
const alias = npmTemplateAliases[relativePath2];
|
|
1917
2123
|
if (!alias)
|
|
1918
2124
|
return source;
|
|
1919
|
-
return
|
|
2125
|
+
return join10(sourceRoot, alias);
|
|
1920
2126
|
}
|
|
1921
2127
|
async function assertGeneratedProjectIsClean(targetDirectory) {
|
|
1922
2128
|
const violations = [];
|
|
@@ -1941,11 +2147,11 @@ function validateProjectName(projectName) {
|
|
|
1941
2147
|
}
|
|
1942
2148
|
async function assertTargetAvailable(targetDirectory) {
|
|
1943
2149
|
try {
|
|
1944
|
-
const entries = await
|
|
2150
|
+
const entries = await readdir5(targetDirectory);
|
|
1945
2151
|
if (entries.length)
|
|
1946
2152
|
throw new Error("目标目录不是空目录");
|
|
1947
2153
|
} catch (error) {
|
|
1948
|
-
if (
|
|
2154
|
+
if (isMissing3(error))
|
|
1949
2155
|
return;
|
|
1950
2156
|
throw error;
|
|
1951
2157
|
}
|
|
@@ -1957,26 +2163,26 @@ async function copyTree(source, target) {
|
|
|
1957
2163
|
return;
|
|
1958
2164
|
if (metadata.isDirectory()) {
|
|
1959
2165
|
await mkdir3(target, { recursive: true });
|
|
1960
|
-
for (const child of await
|
|
1961
|
-
await copyTree(
|
|
2166
|
+
for (const child of await readdir5(source)) {
|
|
2167
|
+
await copyTree(join10(source, child), join10(target, child));
|
|
1962
2168
|
}
|
|
1963
2169
|
return;
|
|
1964
2170
|
}
|
|
1965
|
-
await mkdir3(
|
|
2171
|
+
await mkdir3(join10(target, ".."), { recursive: true });
|
|
1966
2172
|
await copyFile(source, target);
|
|
1967
2173
|
}
|
|
1968
2174
|
function shouldExclude(name) {
|
|
1969
2175
|
return excludedNames.has(name) || excludedRuntimeFiles.has(name);
|
|
1970
2176
|
}
|
|
1971
2177
|
async function walk(directory, visit) {
|
|
1972
|
-
for (const name of await
|
|
1973
|
-
const path =
|
|
2178
|
+
for (const name of await readdir5(directory)) {
|
|
2179
|
+
const path = join10(directory, name);
|
|
1974
2180
|
await visit(path);
|
|
1975
2181
|
if ((await lstat4(path)).isDirectory())
|
|
1976
2182
|
await walk(path, visit);
|
|
1977
2183
|
}
|
|
1978
2184
|
}
|
|
1979
|
-
function
|
|
2185
|
+
function isMissing3(error) {
|
|
1980
2186
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1981
2187
|
}
|
|
1982
2188
|
|
|
@@ -2049,9 +2255,9 @@ async function createProject(options, dependencies = {}) {
|
|
|
2049
2255
|
}
|
|
2050
2256
|
async function inspectTarget(path) {
|
|
2051
2257
|
try {
|
|
2052
|
-
return (await
|
|
2258
|
+
return (await readdir6(path)).length === 0 ? "empty" : "non-empty";
|
|
2053
2259
|
} catch (error) {
|
|
2054
|
-
if (
|
|
2260
|
+
if (isMissing4(error))
|
|
2055
2261
|
return "missing";
|
|
2056
2262
|
throw error;
|
|
2057
2263
|
}
|
|
@@ -2073,7 +2279,7 @@ function stageError(stage, error) {
|
|
|
2073
2279
|
function sanitizeReason(reason) {
|
|
2074
2280
|
return reason.replace(/\b([A-Z][A-Z0-9_]*(?:PASSWORD|SECRET|TOKEN|DATABASE_URL)[A-Z0-9_]*)=\S+/g, "$1=[已隐藏]").replace(/:\/\/([^\s:/]+):([^\s@]+)@/g, "://$1:[已隐藏]@");
|
|
2075
2281
|
}
|
|
2076
|
-
function
|
|
2282
|
+
function isMissing4(error) {
|
|
2077
2283
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
2078
2284
|
}
|
|
2079
2285
|
|
|
@@ -3821,8 +4027,8 @@ import { rm as rm8 } from "node:fs/promises";
|
|
|
3821
4027
|
import { resolve as resolve5 } from "node:path";
|
|
3822
4028
|
|
|
3823
4029
|
// src/update/doctor.ts
|
|
3824
|
-
import { readFile as
|
|
3825
|
-
import { join as
|
|
4030
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
4031
|
+
import { join as join12 } from "node:path";
|
|
3826
4032
|
|
|
3827
4033
|
// src/update/process.ts
|
|
3828
4034
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -3854,12 +4060,12 @@ import {
|
|
|
3854
4060
|
access as access2,
|
|
3855
4061
|
copyFile as copyFile2,
|
|
3856
4062
|
mkdir as mkdir5,
|
|
3857
|
-
readFile as
|
|
4063
|
+
readFile as readFile11,
|
|
3858
4064
|
rm as rm5,
|
|
3859
4065
|
rmdir,
|
|
3860
|
-
writeFile as
|
|
4066
|
+
writeFile as writeFile10
|
|
3861
4067
|
} from "node:fs/promises";
|
|
3862
|
-
import { dirname as dirname3, join as
|
|
4068
|
+
import { dirname as dirname3, join as join11, resolve as resolve4, sep as sep3 } from "node:path";
|
|
3863
4069
|
var updateStatePath = ".windy/update-state.json";
|
|
3864
4070
|
async function applyTransaction(plan) {
|
|
3865
4071
|
if (plan.conflicts.length) {
|
|
@@ -3868,21 +4074,22 @@ async function applyTransaction(plan) {
|
|
|
3868
4074
|
const changed = plan.files.filter(({ action }) => action === "create" || action === "update" || action === "delete");
|
|
3869
4075
|
for (const file of changed)
|
|
3870
4076
|
assertSafePath(file.path);
|
|
3871
|
-
const backupDirectory =
|
|
4077
|
+
const backupDirectory = join11(plan.projectDirectory, ".windy/backups", plan.id);
|
|
3872
4078
|
const protectedPaths = new Set([
|
|
3873
4079
|
...changed.map(({ path: path2 }) => path2),
|
|
3874
4080
|
".windy/generation.json",
|
|
3875
4081
|
".windy/files.json",
|
|
4082
|
+
"apps/web/Dockerfile",
|
|
3876
4083
|
"bun.lock"
|
|
3877
4084
|
]);
|
|
3878
4085
|
const backupEntries = [];
|
|
3879
4086
|
for (const path2 of protectedPaths) {
|
|
3880
|
-
const source =
|
|
4087
|
+
const source = join11(plan.projectDirectory, path2);
|
|
3881
4088
|
const existed = await exists(source);
|
|
3882
4089
|
backupEntries.push({ path: path2, existed });
|
|
3883
4090
|
if (!existed)
|
|
3884
4091
|
continue;
|
|
3885
|
-
const target =
|
|
4092
|
+
const target = join11(backupDirectory, "files", path2);
|
|
3886
4093
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3887
4094
|
await copyFile2(source, target);
|
|
3888
4095
|
}
|
|
@@ -3895,7 +4102,7 @@ async function applyTransaction(plan) {
|
|
|
3895
4102
|
await writeState(plan.projectDirectory, state);
|
|
3896
4103
|
try {
|
|
3897
4104
|
for (const file of changed) {
|
|
3898
|
-
const target =
|
|
4105
|
+
const target = join11(plan.projectDirectory, file.path);
|
|
3899
4106
|
if (file.action === "delete") {
|
|
3900
4107
|
const existed = await exists(target);
|
|
3901
4108
|
await rm5(target, { force: true });
|
|
@@ -3907,7 +4114,7 @@ async function applyTransaction(plan) {
|
|
|
3907
4114
|
if (!file.content)
|
|
3908
4115
|
throw new Error(`更新计划缺少文件内容:${file.path}`);
|
|
3909
4116
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3910
|
-
await
|
|
4117
|
+
await writeFile10(target, file.content);
|
|
3911
4118
|
}
|
|
3912
4119
|
} catch (error) {
|
|
3913
4120
|
await restoreUpdateState(plan.projectDirectory, state);
|
|
@@ -3922,7 +4129,7 @@ async function applyTransaction(plan) {
|
|
|
3922
4129
|
}
|
|
3923
4130
|
async function readUpdateState(projectDirectory) {
|
|
3924
4131
|
try {
|
|
3925
|
-
const parsed = JSON.parse(await
|
|
4132
|
+
const parsed = JSON.parse(await readFile11(join11(projectDirectory, updateStatePath), "utf8"));
|
|
3926
4133
|
if (!isUpdateState(parsed))
|
|
3927
4134
|
throw new Error("更新恢复状态无效,请人工检查 .windy 目录");
|
|
3928
4135
|
return parsed;
|
|
@@ -3935,25 +4142,25 @@ async function readUpdateState(projectDirectory) {
|
|
|
3935
4142
|
async function restoreUpdateState(projectDirectory, state) {
|
|
3936
4143
|
for (const entry of state.entries) {
|
|
3937
4144
|
assertSafePath(entry.path);
|
|
3938
|
-
const target =
|
|
4145
|
+
const target = join11(projectDirectory, entry.path);
|
|
3939
4146
|
if (!entry.existed) {
|
|
3940
4147
|
await rm5(target, { force: true });
|
|
3941
4148
|
continue;
|
|
3942
4149
|
}
|
|
3943
|
-
const backup =
|
|
4150
|
+
const backup = join11(state.backupDirectory, "files", entry.path);
|
|
3944
4151
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3945
4152
|
await copyFile2(backup, target);
|
|
3946
4153
|
}
|
|
3947
|
-
await rm5(
|
|
4154
|
+
await rm5(join11(projectDirectory, updateStatePath), { force: true });
|
|
3948
4155
|
await rm5(state.backupDirectory, { recursive: true, force: true });
|
|
3949
4156
|
}
|
|
3950
4157
|
async function clearUpdateState(projectDirectory) {
|
|
3951
|
-
await rm5(
|
|
4158
|
+
await rm5(join11(projectDirectory, updateStatePath), { force: true });
|
|
3952
4159
|
}
|
|
3953
4160
|
async function writeState(projectDirectory, state) {
|
|
3954
|
-
const path2 =
|
|
4161
|
+
const path2 = join11(projectDirectory, updateStatePath);
|
|
3955
4162
|
await mkdir5(dirname3(path2), { recursive: true });
|
|
3956
|
-
await
|
|
4163
|
+
await writeFile10(path2, `${JSON.stringify(state, null, 2)}
|
|
3957
4164
|
`);
|
|
3958
4165
|
}
|
|
3959
4166
|
async function exists(path2) {
|
|
@@ -4021,7 +4228,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
4021
4228
|
if (file.ownership === "project-owned")
|
|
4022
4229
|
continue;
|
|
4023
4230
|
try {
|
|
4024
|
-
const changed = hashContent(await
|
|
4231
|
+
const changed = hashContent(await readFile12(join12(projectDirectory, file.path))) !== file.hash;
|
|
4025
4232
|
if (!changed)
|
|
4026
4233
|
continue;
|
|
4027
4234
|
modifiedManagedFiles.push(file.path);
|
|
@@ -4070,7 +4277,9 @@ var recipes = [
|
|
|
4070
4277
|
{ id: "starter-0.2.15-to-0.2.16", from: "0.2.15", to: "0.2.16" },
|
|
4071
4278
|
{ id: "starter-0.2.16-to-0.2.17", from: "0.2.16", to: "0.2.17" },
|
|
4072
4279
|
{ id: "starter-0.2.17-to-0.2.18", from: "0.2.17", to: "0.2.18" },
|
|
4073
|
-
{ id: "starter-0.2.18-to-0.2.19", from: "0.2.18", to: "0.2.19" }
|
|
4280
|
+
{ id: "starter-0.2.18-to-0.2.19", from: "0.2.18", to: "0.2.19" },
|
|
4281
|
+
{ id: "starter-0.2.19-to-0.2.20", from: "0.2.19", to: "0.2.20" },
|
|
4282
|
+
{ id: "starter-0.2.20-to-0.2.21", from: "0.2.20", to: "0.2.21" }
|
|
4074
4283
|
];
|
|
4075
4284
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
4076
4285
|
if (sourceVersion === targetVersion)
|
|
@@ -4095,19 +4304,19 @@ function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
|
4095
4304
|
}
|
|
4096
4305
|
|
|
4097
4306
|
// src/update/updater.ts
|
|
4098
|
-
import { mkdir as mkdir7, readFile as
|
|
4099
|
-
import { dirname as dirname4, join as
|
|
4307
|
+
import { mkdir as mkdir7, readFile as readFile16, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
|
|
4308
|
+
import { dirname as dirname4, join as join17 } from "node:path";
|
|
4100
4309
|
|
|
4101
4310
|
// src/update/artifacts.ts
|
|
4102
|
-
import { mkdir as mkdir6, mkdtemp, readFile as
|
|
4311
|
+
import { mkdir as mkdir6, mkdtemp, readFile as readFile13 } from "node:fs/promises";
|
|
4103
4312
|
import { tmpdir } from "node:os";
|
|
4104
|
-
import { basename as basename4, join as
|
|
4313
|
+
import { basename as basename4, join as join13 } from "node:path";
|
|
4105
4314
|
class PublishedArtifactProvider {
|
|
4106
4315
|
async prepare(projectDirectory, provenance) {
|
|
4107
|
-
const temporaryRoot = await mkdtemp(
|
|
4316
|
+
const temporaryRoot = await mkdtemp(join13(tmpdir(), "windy-update-"));
|
|
4108
4317
|
const projectName = await readProjectName2(projectDirectory);
|
|
4109
|
-
const baseDirectory =
|
|
4110
|
-
const targetDirectory =
|
|
4318
|
+
const baseDirectory = join13(temporaryRoot, "base", projectName);
|
|
4319
|
+
const targetDirectory = join13(temporaryRoot, "target", projectName);
|
|
4111
4320
|
await generatePublishedBase(temporaryRoot, projectName, provenance, baseDirectory);
|
|
4112
4321
|
await generateProject({
|
|
4113
4322
|
sourceRoot: await resolveTemplateRoot(),
|
|
@@ -4124,7 +4333,7 @@ class PublishedArtifactProvider {
|
|
|
4124
4333
|
}
|
|
4125
4334
|
}
|
|
4126
4335
|
async function generatePublishedBase(temporaryRoot, projectName, provenance, expectedDirectory) {
|
|
4127
|
-
const parent =
|
|
4336
|
+
const parent = join13(temporaryRoot, "base");
|
|
4128
4337
|
await mkdir6(parent, { recursive: true });
|
|
4129
4338
|
const args = [
|
|
4130
4339
|
"--bun",
|
|
@@ -4148,18 +4357,18 @@ async function generatePublishedBase(temporaryRoot, projectName, provenance, exp
|
|
|
4148
4357
|
}
|
|
4149
4358
|
}
|
|
4150
4359
|
await assertProcess("bunx", args, parent);
|
|
4151
|
-
if (
|
|
4360
|
+
if (join13(parent, projectName) !== expectedDirectory) {
|
|
4152
4361
|
throw new Error("历史 artifact 生成目录异常");
|
|
4153
4362
|
}
|
|
4154
4363
|
}
|
|
4155
4364
|
async function readProjectName2(projectDirectory) {
|
|
4156
|
-
const source = JSON.parse(await
|
|
4365
|
+
const source = JSON.parse(await readFile13(join13(projectDirectory, "package.json"), "utf8"));
|
|
4157
4366
|
return source.name || basename4(projectDirectory);
|
|
4158
4367
|
}
|
|
4159
4368
|
|
|
4160
4369
|
// src/update/planner.ts
|
|
4161
|
-
import { access as access3, readFile as
|
|
4162
|
-
import { join as
|
|
4370
|
+
import { access as access3, readFile as readFile15 } from "node:fs/promises";
|
|
4371
|
+
import { join as join16 } from "node:path";
|
|
4163
4372
|
|
|
4164
4373
|
// src/update/known-repairs.ts
|
|
4165
4374
|
var wrongTextVersions = new Set(["0.2.12", "0.2.13"]);
|
|
@@ -4202,19 +4411,19 @@ function normalizeTableCell(cell) {
|
|
|
4202
4411
|
}
|
|
4203
4412
|
|
|
4204
4413
|
// src/update/merge.ts
|
|
4205
|
-
import { mkdtemp as mkdtemp2, rm as rm6, writeFile as
|
|
4414
|
+
import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile11 } from "node:fs/promises";
|
|
4206
4415
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4207
|
-
import { join as
|
|
4416
|
+
import { join as join14 } from "node:path";
|
|
4208
4417
|
async function mergeTextFile(base, ours, theirs) {
|
|
4209
|
-
const root = await mkdtemp2(
|
|
4418
|
+
const root = await mkdtemp2(join14(tmpdir2(), "windy-merge-"));
|
|
4210
4419
|
try {
|
|
4211
|
-
const oursPath =
|
|
4212
|
-
const basePath =
|
|
4213
|
-
const theirsPath =
|
|
4420
|
+
const oursPath = join14(root, "ours");
|
|
4421
|
+
const basePath = join14(root, "base");
|
|
4422
|
+
const theirsPath = join14(root, "theirs");
|
|
4214
4423
|
await Promise.all([
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4424
|
+
writeFile11(oursPath, ours),
|
|
4425
|
+
writeFile11(basePath, base),
|
|
4426
|
+
writeFile11(theirsPath, theirs)
|
|
4218
4427
|
]);
|
|
4219
4428
|
const result = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
|
|
4220
4429
|
if (result.code === 0) {
|
|
@@ -4382,8 +4591,8 @@ function isTableSeparator(cell) {
|
|
|
4382
4591
|
}
|
|
4383
4592
|
|
|
4384
4593
|
// src/update/migration-reconciler.ts
|
|
4385
|
-
import { readFile as
|
|
4386
|
-
import { join as
|
|
4594
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4595
|
+
import { join as join15 } from "node:path";
|
|
4387
4596
|
|
|
4388
4597
|
// src/update/snapshot-delta.ts
|
|
4389
4598
|
function applySnapshotDelta(before, after, current, path2, conflicts, conflictPath) {
|
|
@@ -4490,7 +4699,7 @@ async function reconcileDrizzleMigrations(input) {
|
|
|
4490
4699
|
const tag = `${prefix}_${name}`;
|
|
4491
4700
|
const sqlPath = `${drizzleRoot}/${tag}.sql`;
|
|
4492
4701
|
const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
|
|
4493
|
-
const sql = await
|
|
4702
|
+
const sql = await readFile14(join15(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
|
|
4494
4703
|
merged.id = targetAfter.id;
|
|
4495
4704
|
merged.prevId = currentSnapshot.id;
|
|
4496
4705
|
const snapshot = encodeJson(merged);
|
|
@@ -4537,13 +4746,13 @@ function unchanged(targetManifest) {
|
|
|
4537
4746
|
async function readJournals(input) {
|
|
4538
4747
|
try {
|
|
4539
4748
|
const [base, project, target] = await Promise.all([
|
|
4540
|
-
readJson(
|
|
4541
|
-
readJson(
|
|
4542
|
-
readJson(
|
|
4749
|
+
readJson(join15(input.baseDirectory, journalPath)),
|
|
4750
|
+
readJson(join15(input.projectDirectory, journalPath)),
|
|
4751
|
+
readJson(join15(input.targetDirectory, journalPath))
|
|
4543
4752
|
]);
|
|
4544
4753
|
return { base, project, target };
|
|
4545
4754
|
} catch (error) {
|
|
4546
|
-
if (
|
|
4755
|
+
if (isMissing5(error))
|
|
4547
4756
|
return;
|
|
4548
4757
|
throw error;
|
|
4549
4758
|
}
|
|
@@ -4551,7 +4760,7 @@ async function readJournals(input) {
|
|
|
4551
4760
|
async function preservePublishedHistory(manifest, projectDirectory, overrides) {
|
|
4552
4761
|
const entries = manifest.files.filter(({ path: path2 }) => isMigrationArtifact(path2) && path2 !== journalPath);
|
|
4553
4762
|
await Promise.all(entries.map(async ({ path: path2 }) => {
|
|
4554
|
-
overrides.set(path2, await
|
|
4763
|
+
overrides.set(path2, await readFile14(join15(projectDirectory, path2)));
|
|
4555
4764
|
}));
|
|
4556
4765
|
return entries;
|
|
4557
4766
|
}
|
|
@@ -4565,7 +4774,7 @@ async function readSnapshot(root, tag) {
|
|
|
4565
4774
|
const match = migrationPattern.exec(tag);
|
|
4566
4775
|
if (!match)
|
|
4567
4776
|
throw new Error(`${journalPath}:migration tag 格式无效:${tag}`);
|
|
4568
|
-
return await readJson(
|
|
4777
|
+
return await readJson(join15(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
|
|
4569
4778
|
}
|
|
4570
4779
|
function validateUniqueNames(journal, label, conflicts) {
|
|
4571
4780
|
const seen = new Set;
|
|
@@ -4606,9 +4815,9 @@ function encodeJson(value) {
|
|
|
4606
4815
|
`);
|
|
4607
4816
|
}
|
|
4608
4817
|
async function readJson(path2) {
|
|
4609
|
-
return JSON.parse(await
|
|
4818
|
+
return JSON.parse(await readFile14(path2, "utf8"));
|
|
4610
4819
|
}
|
|
4611
|
-
function
|
|
4820
|
+
function isMissing5(error) {
|
|
4612
4821
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
4613
4822
|
}
|
|
4614
4823
|
|
|
@@ -4729,9 +4938,9 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
4729
4938
|
}
|
|
4730
4939
|
async function planFile(path2, project, baseRoot, targetRoot, source, target, provenance, targetContentOverride) {
|
|
4731
4940
|
const [ours, base, theirs] = await Promise.all([
|
|
4732
|
-
readOptional(
|
|
4733
|
-
readOptional(
|
|
4734
|
-
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(
|
|
4941
|
+
readOptional(join16(project, path2)),
|
|
4942
|
+
readOptional(join16(baseRoot, path2)),
|
|
4943
|
+
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join16(targetRoot, path2))
|
|
4735
4944
|
]);
|
|
4736
4945
|
const ownership = target?.ownership || source?.ownership || classifyOwnership(path2);
|
|
4737
4946
|
const repaired = ours && theirs && provenance ? repairKnownDerivedFile({
|
|
@@ -4813,7 +5022,7 @@ function conflict(path2, ownership, reason) {
|
|
|
4813
5022
|
}
|
|
4814
5023
|
async function readOptional(path2) {
|
|
4815
5024
|
try {
|
|
4816
|
-
return await
|
|
5025
|
+
return await readFile15(path2);
|
|
4817
5026
|
} catch (error) {
|
|
4818
5027
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4819
5028
|
return;
|
|
@@ -4829,7 +5038,7 @@ async function assertGitClean(projectDirectory) {
|
|
|
4829
5038
|
}
|
|
4830
5039
|
async function assertNoPendingUpdate(projectDirectory) {
|
|
4831
5040
|
try {
|
|
4832
|
-
await access3(
|
|
5041
|
+
await access3(join16(projectDirectory, ".windy/update-state.json"));
|
|
4833
5042
|
throw new Error("检测到未完成更新,请先运行 bun run windy doctor --repair");
|
|
4834
5043
|
} catch (error) {
|
|
4835
5044
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
@@ -4863,9 +5072,17 @@ class DefaultStarterUpdater {
|
|
|
4863
5072
|
async verify(result2) {
|
|
4864
5073
|
const steps = [];
|
|
4865
5074
|
try {
|
|
5075
|
+
const dockerSync = await syncWebDockerfileInProject(result2.plan.projectDirectory, { requireAnchors: true });
|
|
5076
|
+
if (dockerSync.changed) {
|
|
5077
|
+
addChangedFile(result2, "apps/web/Dockerfile");
|
|
5078
|
+
}
|
|
5079
|
+
steps.push({
|
|
5080
|
+
name: "同步 Web Dockerfile workspace COPY 清单",
|
|
5081
|
+
status: dockerSync.synced ? "passed" : "skipped"
|
|
5082
|
+
});
|
|
4866
5083
|
await this.#execute("bun", ["install", "--force"], result2.plan.projectDirectory);
|
|
4867
5084
|
steps.push({ name: "bun install --force", status: "passed" });
|
|
4868
|
-
const packageJson = JSON.parse(await
|
|
5085
|
+
const packageJson = JSON.parse(await readFile16(join17(result2.plan.projectDirectory, "package.json"), "utf8"));
|
|
4869
5086
|
for (const name of ["lint", "typecheck", "test", "build"]) {
|
|
4870
5087
|
if (!packageJson.scripts?.[name]) {
|
|
4871
5088
|
steps.push({ name: `bun run ${name}`, status: "skipped" });
|
|
@@ -4896,12 +5113,12 @@ async function finalize(result2) {
|
|
|
4896
5113
|
const root = result2.plan.projectDirectory;
|
|
4897
5114
|
const generationContent = `${JSON.stringify(result2.plan.targetProvenance, null, 2)}
|
|
4898
5115
|
`;
|
|
4899
|
-
await
|
|
5116
|
+
await writeFile12(join17(root, ".windy/generation.json"), generationContent);
|
|
4900
5117
|
const refreshedPaths = new Set([
|
|
4901
5118
|
".windy/generation.json",
|
|
4902
|
-
...result2.
|
|
5119
|
+
...result2.changedFiles
|
|
4903
5120
|
]);
|
|
4904
|
-
const refreshedHashes = new Map(await Promise.all([...refreshedPaths].map(async (path2) => [path2, hashContent(await
|
|
5121
|
+
const refreshedHashes = new Map(await Promise.all([...refreshedPaths].map(async (path2) => [path2, hashContent(await readFile16(join17(root, path2)))])));
|
|
4905
5122
|
const targetManifest = {
|
|
4906
5123
|
...result2.plan.targetManifest,
|
|
4907
5124
|
files: result2.plan.targetManifest.files.map((entry) => ({
|
|
@@ -4909,11 +5126,15 @@ async function finalize(result2) {
|
|
|
4909
5126
|
hash: refreshedHashes.get(entry.path) ?? entry.hash
|
|
4910
5127
|
}))
|
|
4911
5128
|
};
|
|
4912
|
-
await
|
|
5129
|
+
await writeFile12(join17(root, ".windy/files.json"), `${JSON.stringify(targetManifest, null, 2)}
|
|
4913
5130
|
`);
|
|
4914
5131
|
}
|
|
5132
|
+
function addChangedFile(result2, path2) {
|
|
5133
|
+
if (!result2.changedFiles.includes(path2))
|
|
5134
|
+
result2.changedFiles.push(path2);
|
|
5135
|
+
}
|
|
4915
5136
|
async function writeReport(result2, steps, relativePath2) {
|
|
4916
|
-
const path2 =
|
|
5137
|
+
const path2 = join17(result2.plan.projectDirectory, relativePath2);
|
|
4917
5138
|
await mkdir7(dirname4(path2), { recursive: true });
|
|
4918
5139
|
const lines = [
|
|
4919
5140
|
`# Windy 更新报告 ${result2.plan.id}`,
|
|
@@ -4931,16 +5152,27 @@ async function writeReport(result2, steps, relativePath2) {
|
|
|
4931
5152
|
`备份保留在 \`.windy/backups/${result2.plan.id}\`,确认稳定后可人工删除。`,
|
|
4932
5153
|
""
|
|
4933
5154
|
];
|
|
4934
|
-
await
|
|
5155
|
+
await writeFile12(path2, lines.join(`
|
|
4935
5156
|
`));
|
|
4936
5157
|
}
|
|
4937
5158
|
|
|
4938
5159
|
// src/update/cli.ts
|
|
4939
5160
|
async function runLifecycleCommand(args) {
|
|
4940
5161
|
const command = args[0];
|
|
4941
|
-
if (command !== "doctor" && command !== "update")
|
|
5162
|
+
if (command !== "doctor" && command !== "update" && command !== "sync-docker") {
|
|
4942
5163
|
return false;
|
|
5164
|
+
}
|
|
4943
5165
|
const projectDirectory = resolve5(readValue2(args, "--cwd") || process.cwd());
|
|
5166
|
+
if (command === "sync-docker") {
|
|
5167
|
+
const result2 = await syncWebDockerfileInProject(projectDirectory, {
|
|
5168
|
+
requireAnchors: true
|
|
5169
|
+
});
|
|
5170
|
+
if (!result2.synced) {
|
|
5171
|
+
throw new Error("项目缺少 apps/web/Dockerfile,无法同步 COPY 清单");
|
|
5172
|
+
}
|
|
5173
|
+
console.log(result2.changed ? "已同步 apps/web/Dockerfile workspace COPY 清单" : "apps/web/Dockerfile workspace COPY 清单已是最新");
|
|
5174
|
+
return true;
|
|
5175
|
+
}
|
|
4944
5176
|
if (command === "doctor") {
|
|
4945
5177
|
const report = await diagnoseProject(projectDirectory, args.includes("--repair"));
|
|
4946
5178
|
printDoctor(report);
|