create-windy 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/cli.js +312 -154
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/server/src/application-services.ts +2 -2
- package/template/apps/server/src/audit/search-summary.ts +15 -8
- package/template/apps/server/src/example-modules.ts +58 -70
- package/template/apps/server/src/foundation.ts +8 -4
- package/template/apps/server/src/guards.test.ts +13 -0
- package/template/apps/server/src/guards.ts +12 -2
- package/template/apps/server/src/index.ts +48 -91
- package/template/apps/server/src/installed-business-modules.ts +23 -4
- package/template/apps/server/src/module-bootstrap.ts +83 -0
- package/template/apps/server/src/module-composition.test.ts +45 -1
- package/template/apps/server/src/module-composition.ts +45 -0
- package/template/apps/server/src/module-host/initialize.test.ts +123 -0
- package/template/apps/server/src/module-host/initialize.ts +331 -0
- package/template/apps/server/src/module-host/request-context.test.ts +66 -0
- package/template/apps/server/src/module-host/request-context.ts +156 -0
- package/template/apps/server/src/module-host/role-presets.test.ts +94 -0
- package/template/apps/server/src/module-host/role-presets.ts +76 -0
- package/template/apps/server/src/module-host/route-installer.test.ts +317 -0
- package/template/apps/server/src/module-host/route-installer.ts +97 -0
- package/template/apps/server/src/module-host/route-registration.test.ts +160 -0
- package/template/apps/server/src/module-host/search-providers.ts +31 -0
- package/template/apps/server/src/module-host/storage-port.test.ts +165 -0
- package/template/apps/server/src/module-host/storage-port.ts +59 -0
- package/template/apps/server/src/module-host/task-definitions.ts +41 -0
- package/template/apps/server/src/module-host/test-fixtures.ts +26 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +25 -0
- package/template/apps/server/src/module-host.test.ts +202 -58
- package/template/apps/server/src/module-host.ts +34 -113
- package/template/apps/server/src/route-guards.test.ts +112 -1
- package/template/apps/server/src/runtime-feature.ts +66 -31
- package/template/apps/server/src/runtime.test.ts +1 -0
- package/template/apps/server/src/runtime.ts +3 -1
- package/template/apps/server/src/system/built-in-roles.ts +1 -1
- package/template/apps/server/src/work-order/foundation-modules.test.ts +30 -10
- package/template/apps/server/src/work-order/routes.test.ts +30 -18
- package/template/apps/server/src/work-order/routes.ts +104 -135
- package/template/apps/server/src/work-order/search-api.test.ts +14 -3
- package/template/apps/server/src/work-order/search-provider.test.ts +23 -15
- package/template/apps/server/src/work-order/search-provider.ts +17 -12
- package/template/apps/server/src/work-order/task.test.ts +16 -11
- package/template/apps/web/Dockerfile +4 -1
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +4 -1
- package/template/apps/web/src/layout/GlobalWatermark.vue +2 -0
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +4 -1
- package/template/docs/architecture/object-storage.md +12 -0
- package/template/packages/config/index.test.ts +57 -0
- package/template/packages/config/package.json +1 -1
- package/template/packages/config/src/platform.ts +28 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/runner.ts +7 -9
- package/template/packages/example-work-order/index.ts +1 -0
- package/template/packages/example-work-order/package.json +2 -0
- package/template/packages/example-work-order/src/server-module.ts +61 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs/src/types.ts +7 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/catalog-validation.test.ts +212 -0
- package/template/packages/modules/src/catalog-validation.ts +19 -2
- package/template/packages/modules/src/migration-bundle-validation.test.ts +191 -0
- package/template/packages/server-sdk/index.ts +42 -0
- package/template/packages/server-sdk/package.json +16 -3
- package/template/packages/server-sdk/src/actor.ts +15 -0
- package/template/packages/server-sdk/src/audit-port.ts +22 -0
- package/template/packages/server-sdk/src/jobs-port.ts +24 -0
- package/template/packages/server-sdk/src/module-host.ts +36 -1
- package/template/packages/server-sdk/src/namespace.test.ts +26 -0
- package/template/packages/server-sdk/src/namespace.ts +13 -0
- package/template/packages/server-sdk/src/request-context.ts +21 -0
- package/template/packages/server-sdk/src/role-preset.ts +18 -0
- package/template/packages/server-sdk/src/route-definition.ts +26 -0
- package/template/packages/server-sdk/src/search-provider-port.ts +48 -0
- package/template/packages/server-sdk/src/storage-port.ts +25 -0
- package/template/packages/server-sdk/src/task-registration.ts +18 -0
- package/template/packages/server-sdk/src/tool-registration.ts +29 -0
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/license-catalog.test.ts +73 -1
- package/template/packages/shared/src/license-catalog.ts +95 -0
- package/template/packages/{database/src/bundle-validation.ts → shared/src/migration-bundle-validation.ts} +2 -1
- package/template/apps/server/src/work-order/task.ts +0 -30
package/dist/cli.js
CHANGED
|
@@ -727,7 +727,7 @@ var helpText = `create-windy <项目名> [选项]
|
|
|
727
727
|
`;
|
|
728
728
|
|
|
729
729
|
// src/create-project.ts
|
|
730
|
-
import { mkdir as mkdir4, readdir as
|
|
730
|
+
import { mkdir as mkdir4, readdir as readdir6, rm as rm4 } from "node:fs/promises";
|
|
731
731
|
|
|
732
732
|
// src/commands.ts
|
|
733
733
|
import { spawn } from "node:child_process";
|
|
@@ -920,16 +920,147 @@ import {
|
|
|
920
920
|
copyFile,
|
|
921
921
|
lstat as lstat4,
|
|
922
922
|
mkdir as mkdir3,
|
|
923
|
-
readFile as
|
|
924
|
-
readdir as
|
|
923
|
+
readFile as readFile10,
|
|
924
|
+
readdir as readdir5,
|
|
925
925
|
rm as rm3,
|
|
926
|
-
writeFile as
|
|
926
|
+
writeFile as writeFile9
|
|
927
927
|
} from "node:fs/promises";
|
|
928
|
-
import { basename as basename3, extname as extname3, join as
|
|
928
|
+
import { basename as basename3, extname as extname3, join as join10 } from "node:path";
|
|
929
929
|
|
|
930
930
|
// src/customer-docker.ts
|
|
931
|
-
import { readFile as
|
|
931
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
932
|
+
import { join as join3 } from "node:path";
|
|
933
|
+
|
|
934
|
+
// src/docker-copy-sync.ts
|
|
935
|
+
import { readdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
932
936
|
import { join as join2 } from "node:path";
|
|
937
|
+
var webDockerfilePath = "apps/web/Dockerfile";
|
|
938
|
+
var webEntryDirectory = "apps/web";
|
|
939
|
+
var depsCopyAnchor = "windy-copy:deps";
|
|
940
|
+
var buildCopyAnchor = "windy-copy:build";
|
|
941
|
+
async function listWorkspacePackages(root) {
|
|
942
|
+
const manifest = await readManifest(join2(root, "package.json"));
|
|
943
|
+
const packages = [];
|
|
944
|
+
for (const pattern of manifest.workspaces ?? []) {
|
|
945
|
+
if (!pattern.endsWith("/*")) {
|
|
946
|
+
throw new Error(`暂不支持的 workspace pattern: ${pattern}`);
|
|
947
|
+
}
|
|
948
|
+
const base = pattern.slice(0, -2);
|
|
949
|
+
const entries = await readdir(join2(root, base), {
|
|
950
|
+
withFileTypes: true
|
|
951
|
+
}).catch((error) => {
|
|
952
|
+
if (isMissing(error))
|
|
953
|
+
return [];
|
|
954
|
+
throw error;
|
|
955
|
+
});
|
|
956
|
+
for (const entry of entries) {
|
|
957
|
+
if (!entry.isDirectory())
|
|
958
|
+
continue;
|
|
959
|
+
const directory = `${base}/${entry.name}`;
|
|
960
|
+
const manifestPath = join2(root, directory, "package.json");
|
|
961
|
+
if (!await Bun.file(manifestPath).exists())
|
|
962
|
+
continue;
|
|
963
|
+
const value = await readManifest(manifestPath);
|
|
964
|
+
packages.push({
|
|
965
|
+
name: value.name,
|
|
966
|
+
directory,
|
|
967
|
+
dependencies: [
|
|
968
|
+
...Object.keys(value.dependencies ?? {}),
|
|
969
|
+
...Object.keys(value.devDependencies ?? {})
|
|
970
|
+
]
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return packages.sort((a, b) => a.directory.localeCompare(b.directory));
|
|
975
|
+
}
|
|
976
|
+
function workspaceDependencyClosure(packages, entryDirectory) {
|
|
977
|
+
const directoryByName = new Map;
|
|
978
|
+
const packageByDirectory = new Map;
|
|
979
|
+
for (const pkg of packages) {
|
|
980
|
+
if (pkg.name)
|
|
981
|
+
directoryByName.set(pkg.name, pkg.directory);
|
|
982
|
+
packageByDirectory.set(pkg.directory, pkg);
|
|
983
|
+
}
|
|
984
|
+
const visited = new Set([entryDirectory]);
|
|
985
|
+
const queue = [entryDirectory];
|
|
986
|
+
while (queue.length) {
|
|
987
|
+
const current = packageByDirectory.get(queue.shift());
|
|
988
|
+
if (!current)
|
|
989
|
+
continue;
|
|
990
|
+
for (const dependency of current.dependencies) {
|
|
991
|
+
const directory = directoryByName.get(dependency);
|
|
992
|
+
if (directory && !visited.has(directory)) {
|
|
993
|
+
visited.add(directory);
|
|
994
|
+
queue.push(directory);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return [...visited].sort((a, b) => a.localeCompare(b));
|
|
999
|
+
}
|
|
1000
|
+
function dockerCopyLines(directories) {
|
|
1001
|
+
return directories.map((directory) => `COPY ${directory} ${directory}`);
|
|
1002
|
+
}
|
|
1003
|
+
function dockerManifestCopyLines(directories) {
|
|
1004
|
+
return directories.map((directory) => `COPY ${directory}/package.json ${directory}/package.json`);
|
|
1005
|
+
}
|
|
1006
|
+
function rewriteAnchoredSection(source, anchor, lines) {
|
|
1007
|
+
const { begin, end } = locateAnchoredSection(source, anchor);
|
|
1008
|
+
const all = source.split(`
|
|
1009
|
+
`);
|
|
1010
|
+
return [...all.slice(0, begin + 1), ...lines, ...all.slice(end)].join(`
|
|
1011
|
+
`);
|
|
1012
|
+
}
|
|
1013
|
+
function hasCopyAnchors(source) {
|
|
1014
|
+
return source.includes(`# ${depsCopyAnchor} begin`) && source.includes(`# ${buildCopyAnchor} begin`);
|
|
1015
|
+
}
|
|
1016
|
+
function syncWebDockerfileSource(source, workspaceDirectories, buildDirectories) {
|
|
1017
|
+
const withDeps = rewriteAnchoredSection(source, depsCopyAnchor, dockerManifestCopyLines(workspaceDirectories));
|
|
1018
|
+
return rewriteAnchoredSection(withDeps, buildCopyAnchor, dockerCopyLines(buildDirectories));
|
|
1019
|
+
}
|
|
1020
|
+
async function syncWebDockerfileInProject(root, options = {}) {
|
|
1021
|
+
const path = join2(root, webDockerfilePath);
|
|
1022
|
+
let source;
|
|
1023
|
+
try {
|
|
1024
|
+
source = await readFile2(path, "utf8");
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
if (isMissing(error))
|
|
1027
|
+
return { synced: false, changed: false };
|
|
1028
|
+
throw error;
|
|
1029
|
+
}
|
|
1030
|
+
if (!hasCopyAnchors(source)) {
|
|
1031
|
+
if (!options.requireAnchors)
|
|
1032
|
+
return { synced: false, changed: false };
|
|
1033
|
+
throw new Error(`${webDockerfilePath} 缺少 # ${depsCopyAnchor} / # ${buildCopyAnchor} 锚点段,` + `请先按最新模板为 deps 与 build 阶段的 COPY 段添加锚点后再同步`);
|
|
1034
|
+
}
|
|
1035
|
+
const packages = await listWorkspacePackages(root);
|
|
1036
|
+
const next = syncWebDockerfileSource(source, packages.map(({ directory }) => directory), workspaceDependencyClosure(packages, webEntryDirectory));
|
|
1037
|
+
if (next !== source)
|
|
1038
|
+
await writeFile(path, next);
|
|
1039
|
+
return { synced: true, changed: next !== source };
|
|
1040
|
+
}
|
|
1041
|
+
function locateAnchoredSection(source, anchor) {
|
|
1042
|
+
const beginMarker = `# ${anchor} begin`;
|
|
1043
|
+
const endMarker = `# ${anchor} end`;
|
|
1044
|
+
const lines = source.split(`
|
|
1045
|
+
`);
|
|
1046
|
+
const begin = lines.indexOf(beginMarker);
|
|
1047
|
+
const end = lines.indexOf(endMarker);
|
|
1048
|
+
if (begin === -1 || end === -1 || end <= begin) {
|
|
1049
|
+
throw new Error(`Dockerfile 缺少锚点段 ${beginMarker} ... ${endMarker},请先迁移到锚点格式`);
|
|
1050
|
+
}
|
|
1051
|
+
if (lines.indexOf(beginMarker, begin + 1) !== -1 || lines.indexOf(endMarker, end + 1) !== -1) {
|
|
1052
|
+
throw new Error(`Dockerfile 锚点段 ${anchor} 重复,请保留唯一一段`);
|
|
1053
|
+
}
|
|
1054
|
+
return { begin, end };
|
|
1055
|
+
}
|
|
1056
|
+
async function readManifest(path) {
|
|
1057
|
+
return JSON.parse(await readFile2(path, "utf8"));
|
|
1058
|
+
}
|
|
1059
|
+
function isMissing(error) {
|
|
1060
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/customer-docker.ts
|
|
933
1064
|
var supplierWorkspaceCopies = [
|
|
934
1065
|
"COPY apps/license/package.json apps/license/package.json",
|
|
935
1066
|
"COPY apps/signing-service/package.json apps/signing-service/package.json",
|
|
@@ -937,11 +1068,12 @@ var supplierWorkspaceCopies = [
|
|
|
937
1068
|
];
|
|
938
1069
|
var supplierWorkspacePaths = ["packages/license-sdk"];
|
|
939
1070
|
async function prepareCustomerDockerfiles(root) {
|
|
940
|
-
await Promise.all(["apps/server/Dockerfile",
|
|
941
|
-
const absolutePath =
|
|
942
|
-
const source = await
|
|
943
|
-
await
|
|
1071
|
+
await Promise.all(["apps/server/Dockerfile", webDockerfilePath].map(async (path) => {
|
|
1072
|
+
const absolutePath = join3(root, path);
|
|
1073
|
+
const source = await readFile3(absolutePath, "utf8");
|
|
1074
|
+
await writeFile2(absolutePath, rewriteCustomerDockerfile(source));
|
|
944
1075
|
}));
|
|
1076
|
+
await syncWebDockerfileInProject(root, { requireAnchors: true });
|
|
945
1077
|
}
|
|
946
1078
|
function rewriteCustomerDockerfile(source) {
|
|
947
1079
|
const lines = source.split(`
|
|
@@ -953,8 +1085,8 @@ function rewriteCustomerDockerfile(source) {
|
|
|
953
1085
|
}
|
|
954
1086
|
|
|
955
1087
|
// src/code-quality.ts
|
|
956
|
-
import { readFile as
|
|
957
|
-
import { join as
|
|
1088
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
1089
|
+
import { join as join4 } from "node:path";
|
|
958
1090
|
var oxcStarterPolicy = [
|
|
959
1091
|
"- 生成时默认预装 Oxlint、Oxfmt、lint-staged 与 Husky。",
|
|
960
1092
|
"- 提交前自动格式化暂存文件;lint、typecheck 和 test 保留为显式命令。",
|
|
@@ -963,30 +1095,30 @@ var oxcStarterPolicy = [
|
|
|
963
1095
|
`);
|
|
964
1096
|
var noCodeQualityPolicy = "- 本项目生成时未引入 lint 或 format 工具;项目所有者可按团队需要自行选择。";
|
|
965
1097
|
async function reflectCodeQualityChoice(targetDirectory, includeOxc) {
|
|
966
|
-
const path =
|
|
967
|
-
const source = await
|
|
1098
|
+
const path = join4(targetDirectory, "AGENTS.md");
|
|
1099
|
+
const source = await readFile4(path, "utf8");
|
|
968
1100
|
if (includeOxc)
|
|
969
1101
|
return;
|
|
970
1102
|
if (!source.includes(oxcStarterPolicy)) {
|
|
971
1103
|
throw new Error("无法同步 --no-oxc 的项目开发约束");
|
|
972
1104
|
}
|
|
973
|
-
await
|
|
1105
|
+
await writeFile3(path, source.replace(oxcStarterPolicy, noCodeQualityPolicy));
|
|
974
1106
|
}
|
|
975
1107
|
|
|
976
1108
|
// src/license-files.ts
|
|
977
|
-
import { mkdir, readFile as
|
|
978
|
-
import { dirname, join as
|
|
1109
|
+
import { mkdir, readFile as readFile5, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
1110
|
+
import { dirname, join as join5, resolve as resolve2 } from "node:path";
|
|
979
1111
|
import { fileURLToPath } from "node:url";
|
|
980
1112
|
var packageRoot = resolve2(dirname(fileURLToPath(import.meta.url)), "..");
|
|
981
1113
|
var apacheLicensePath = resolve2(packageRoot, "LICENSE");
|
|
982
1114
|
var windyLicensePath = ".windy/licenses/WINDY-APACHE-2.0.txt";
|
|
983
1115
|
async function applyWindyBaseLicense(targetDirectory) {
|
|
984
|
-
const target =
|
|
1116
|
+
const target = join5(targetDirectory, windyLicensePath);
|
|
985
1117
|
await mkdir(dirname(target), { recursive: true });
|
|
986
|
-
await
|
|
1118
|
+
await writeFile4(target, await readFile5(apacheLicensePath));
|
|
987
1119
|
}
|
|
988
1120
|
async function applyProjectLicense(targetDirectory, license, holder, year = new Date().getFullYear()) {
|
|
989
|
-
const target =
|
|
1121
|
+
const target = join5(targetDirectory, "LICENSE");
|
|
990
1122
|
if (license === "UNLICENSED") {
|
|
991
1123
|
await rm(target, { force: true });
|
|
992
1124
|
return;
|
|
@@ -996,11 +1128,11 @@ async function applyProjectLicense(targetDirectory, license, holder, year = new
|
|
|
996
1128
|
if (!owner)
|
|
997
1129
|
throw new Error("选择 MIT 时必须填写许可证权利人名称");
|
|
998
1130
|
await mkdir(targetDirectory, { recursive: true });
|
|
999
|
-
await
|
|
1131
|
+
await writeFile4(target, mitLicense(year, owner));
|
|
1000
1132
|
return;
|
|
1001
1133
|
}
|
|
1002
1134
|
await mkdir(targetDirectory, { recursive: true });
|
|
1003
|
-
await
|
|
1135
|
+
await writeFile4(target, await readFile5(apacheLicensePath));
|
|
1004
1136
|
}
|
|
1005
1137
|
function mitLicense(year, holder) {
|
|
1006
1138
|
return `MIT License
|
|
@@ -1029,8 +1161,8 @@ SOFTWARE.
|
|
|
1029
1161
|
|
|
1030
1162
|
// src/managed-files.ts
|
|
1031
1163
|
import { createHash } from "node:crypto";
|
|
1032
|
-
import { lstat, readdir, readFile as
|
|
1033
|
-
import { join as
|
|
1164
|
+
import { lstat, readdir as readdir2, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
1165
|
+
import { join as join6, relative, sep } from "node:path";
|
|
1034
1166
|
var managedFilesManifestPath = ".windy/files.json";
|
|
1035
1167
|
var ignoredDirectoryNames = new Set([".git", "node_modules", "dist"]);
|
|
1036
1168
|
var ignoredFiles = new Set([
|
|
@@ -1060,7 +1192,7 @@ var sharedScaffoldRoots = [
|
|
|
1060
1192
|
async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
|
|
1061
1193
|
const paths = await collectFiles(targetDirectory);
|
|
1062
1194
|
const files = await Promise.all(paths.map(async (path) => {
|
|
1063
|
-
const content = await
|
|
1195
|
+
const content = await readFile6(join6(targetDirectory, path));
|
|
1064
1196
|
return {
|
|
1065
1197
|
path,
|
|
1066
1198
|
ownership: classifyOwnership(path),
|
|
@@ -1073,11 +1205,11 @@ async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
|
|
|
1073
1205
|
algorithm: "sha256",
|
|
1074
1206
|
files
|
|
1075
1207
|
};
|
|
1076
|
-
await
|
|
1208
|
+
await writeFile5(join6(targetDirectory, managedFilesManifestPath), `${JSON.stringify(manifest, null, 2)}
|
|
1077
1209
|
`);
|
|
1078
1210
|
}
|
|
1079
1211
|
async function readManagedFilesManifest(targetDirectory) {
|
|
1080
|
-
const parsed = JSON.parse(await
|
|
1212
|
+
const parsed = JSON.parse(await readFile6(join6(targetDirectory, managedFilesManifestPath), "utf8"));
|
|
1081
1213
|
if (!isManagedFilesManifest(parsed)) {
|
|
1082
1214
|
throw new Error("受管文件清单无效,无法安全更新");
|
|
1083
1215
|
}
|
|
@@ -1098,10 +1230,10 @@ async function collectFiles(root) {
|
|
|
1098
1230
|
await visit(root, files);
|
|
1099
1231
|
return files.sort();
|
|
1100
1232
|
async function visit(directory, output) {
|
|
1101
|
-
for (const name of (await
|
|
1233
|
+
for (const name of (await readdir2(directory)).sort()) {
|
|
1102
1234
|
if (ignoredDirectoryNames.has(name))
|
|
1103
1235
|
continue;
|
|
1104
|
-
const path =
|
|
1236
|
+
const path = join6(directory, name);
|
|
1105
1237
|
const metadata = await lstat(path);
|
|
1106
1238
|
if (metadata.isSymbolicLink())
|
|
1107
1239
|
continue;
|
|
@@ -1135,8 +1267,8 @@ function isManagedFilesManifest(value) {
|
|
|
1135
1267
|
}
|
|
1136
1268
|
|
|
1137
1269
|
// src/module-materializer.ts
|
|
1138
|
-
import { lstat as lstat2, readFile as
|
|
1139
|
-
import { basename, extname, join as
|
|
1270
|
+
import { lstat as lstat2, readFile as readFile7, readdir as readdir3, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
|
|
1271
|
+
import { basename, extname, join as join7, relative as relative2, sep as sep2 } from "node:path";
|
|
1140
1272
|
var textExtensions = new Set([
|
|
1141
1273
|
".css",
|
|
1142
1274
|
".html",
|
|
@@ -1169,18 +1301,18 @@ async function materializeSelectedModules(targetDirectory, selectedNames) {
|
|
|
1169
1301
|
...manifest.documentation,
|
|
1170
1302
|
...manifest.schemaFiles
|
|
1171
1303
|
]);
|
|
1172
|
-
await Promise.all([...new Set(removedRoots)].map((path) => rm2(
|
|
1304
|
+
await Promise.all([...new Set(removedRoots)].map((path) => rm2(join7(targetDirectory, path), { recursive: true, force: true })));
|
|
1173
1305
|
await pruneWorkspaceReferences(targetDirectory, removedPackageNames, removedRoots);
|
|
1174
1306
|
}
|
|
1175
1307
|
async function stripModuleBlocks(root, selected) {
|
|
1176
1308
|
for (const path of await collectFiles2(root)) {
|
|
1177
1309
|
if (!textExtensions.has(extname(path)))
|
|
1178
1310
|
continue;
|
|
1179
|
-
const source = await
|
|
1311
|
+
const source = await readFile7(path, "utf8");
|
|
1180
1312
|
if (!source.includes("@windy-module"))
|
|
1181
1313
|
continue;
|
|
1182
1314
|
const output = stripTaggedSource(source, selected, relativePath(root, path));
|
|
1183
|
-
await
|
|
1315
|
+
await writeFile6(path, output);
|
|
1184
1316
|
}
|
|
1185
1317
|
}
|
|
1186
1318
|
function stripTaggedSource(source, selected, path = "source") {
|
|
@@ -1213,11 +1345,11 @@ async function readRemovedPackageNames(root, roots) {
|
|
|
1213
1345
|
const names = new Set;
|
|
1214
1346
|
for (const packageRoot2 of roots) {
|
|
1215
1347
|
try {
|
|
1216
|
-
const value = JSON.parse(await
|
|
1348
|
+
const value = JSON.parse(await readFile7(join7(root, packageRoot2, "package.json"), "utf8"));
|
|
1217
1349
|
if (value.name)
|
|
1218
1350
|
names.add(value.name);
|
|
1219
1351
|
} catch (error) {
|
|
1220
|
-
if (!
|
|
1352
|
+
if (!isMissing2(error))
|
|
1221
1353
|
throw error;
|
|
1222
1354
|
}
|
|
1223
1355
|
}
|
|
@@ -1226,7 +1358,7 @@ async function readRemovedPackageNames(root, roots) {
|
|
|
1226
1358
|
async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
|
|
1227
1359
|
for (const path of await collectFiles2(root)) {
|
|
1228
1360
|
if (basename(path) === "package.json") {
|
|
1229
|
-
const value = JSON.parse(await
|
|
1361
|
+
const value = JSON.parse(await readFile7(path, "utf8"));
|
|
1230
1362
|
let changed = false;
|
|
1231
1363
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
1232
1364
|
const dependencies = value[section];
|
|
@@ -1238,45 +1370,45 @@ async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
|
|
|
1238
1370
|
}
|
|
1239
1371
|
}
|
|
1240
1372
|
if (changed)
|
|
1241
|
-
await
|
|
1373
|
+
await writeFile6(path, `${JSON.stringify(value, null, 2)}
|
|
1242
1374
|
`);
|
|
1243
1375
|
continue;
|
|
1244
1376
|
}
|
|
1245
1377
|
if (basename(path) !== "Dockerfile")
|
|
1246
1378
|
continue;
|
|
1247
|
-
const source = await
|
|
1379
|
+
const source = await readFile7(path, "utf8");
|
|
1248
1380
|
const output = source.split(`
|
|
1249
1381
|
`).filter((line) => !removedRoots.some((root2) => line.includes(root2))).join(`
|
|
1250
1382
|
`);
|
|
1251
1383
|
if (output !== source)
|
|
1252
|
-
await
|
|
1384
|
+
await writeFile6(path, output);
|
|
1253
1385
|
}
|
|
1254
1386
|
}
|
|
1255
1387
|
async function pruneDatabaseArtifacts(root, tableNames, statementTokens) {
|
|
1256
1388
|
if (!tableNames.length && !statementTokens.length)
|
|
1257
1389
|
return;
|
|
1258
1390
|
const tablePatterns = tableNames.map((name) => new RegExp(`(?:"${escapeRegExp(name)}"|\\b${escapeRegExp(name)}\\b)`));
|
|
1259
|
-
const drizzleRoot =
|
|
1391
|
+
const drizzleRoot = join7(root, "packages/database/drizzle");
|
|
1260
1392
|
try {
|
|
1261
1393
|
for (const path of await collectFiles2(drizzleRoot)) {
|
|
1262
1394
|
if (path.endsWith(".sql")) {
|
|
1263
|
-
const source = await
|
|
1395
|
+
const source = await readFile7(path, "utf8");
|
|
1264
1396
|
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
|
|
1397
|
+
await writeFile6(path, statements.length ? `${statements.join(`
|
|
1266
1398
|
--> statement-breakpoint
|
|
1267
1399
|
`)}
|
|
1268
1400
|
` : `-- 此 migration 的模块未被当前项目选择。
|
|
1269
1401
|
`);
|
|
1270
1402
|
} else if (path.endsWith("_snapshot.json")) {
|
|
1271
|
-
const snapshot = JSON.parse(await
|
|
1403
|
+
const snapshot = JSON.parse(await readFile7(path, "utf8"));
|
|
1272
1404
|
for (const name of tableNames)
|
|
1273
1405
|
delete snapshot.tables?.[`public.${name}`];
|
|
1274
|
-
await
|
|
1406
|
+
await writeFile6(path, `${JSON.stringify(snapshot, null, 2)}
|
|
1275
1407
|
`);
|
|
1276
1408
|
}
|
|
1277
1409
|
}
|
|
1278
1410
|
} catch (error) {
|
|
1279
|
-
if (!
|
|
1411
|
+
if (!isMissing2(error))
|
|
1280
1412
|
throw error;
|
|
1281
1413
|
}
|
|
1282
1414
|
}
|
|
@@ -1285,10 +1417,10 @@ async function collectFiles2(root) {
|
|
|
1285
1417
|
await visit(root);
|
|
1286
1418
|
return files.sort();
|
|
1287
1419
|
async function visit(directory) {
|
|
1288
|
-
for (const name of (await
|
|
1420
|
+
for (const name of (await readdir3(directory)).sort()) {
|
|
1289
1421
|
if ([".git", "node_modules", "dist"].includes(name))
|
|
1290
1422
|
continue;
|
|
1291
|
-
const path =
|
|
1423
|
+
const path = join7(directory, name);
|
|
1292
1424
|
const metadata = await lstat2(path);
|
|
1293
1425
|
if (metadata.isSymbolicLink())
|
|
1294
1426
|
continue;
|
|
@@ -1305,7 +1437,7 @@ function relativePath(root, path) {
|
|
|
1305
1437
|
function escapeRegExp(value) {
|
|
1306
1438
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1307
1439
|
}
|
|
1308
|
-
function
|
|
1440
|
+
function isMissing2(error) {
|
|
1309
1441
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1310
1442
|
}
|
|
1311
1443
|
|
|
@@ -1604,7 +1736,8 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
|
|
|
1604
1736
|
prepare: "git rev-parse --git-dir >/dev/null 2>&1 || exit 0; husky"
|
|
1605
1737
|
} : {},
|
|
1606
1738
|
dev: "docker compose up",
|
|
1607
|
-
"
|
|
1739
|
+
"docker:sync": "create-windy sync-docker",
|
|
1740
|
+
"dev:build": "bun run docker:sync && docker compose up --build",
|
|
1608
1741
|
...generatorVersion ? { windy: "create-windy" } : {},
|
|
1609
1742
|
"dev:web": "bun run --cwd apps/web dev",
|
|
1610
1743
|
"dev:server": "bun run --cwd apps/server dev",
|
|
@@ -1725,21 +1858,21 @@ var excludedRuntimeFiles = new Set([
|
|
|
1725
1858
|
]);
|
|
1726
1859
|
|
|
1727
1860
|
// src/provenance.ts
|
|
1728
|
-
import { mkdir as mkdir2, readFile as
|
|
1729
|
-
import { join as
|
|
1861
|
+
import { mkdir as mkdir2, readFile as readFile8, writeFile as writeFile7 } from "node:fs/promises";
|
|
1862
|
+
import { join as join8 } from "node:path";
|
|
1730
1863
|
var templateMetadataFile = ".windy-template.json";
|
|
1731
1864
|
var generationMetadataFile = ".windy/generation.json";
|
|
1732
1865
|
async function readTemplateMetadata(sourceRoot) {
|
|
1733
|
-
const path =
|
|
1734
|
-
const parsed = JSON.parse(await
|
|
1866
|
+
const path = join8(sourceRoot, templateMetadataFile);
|
|
1867
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
1735
1868
|
if (!isTemplateMetadata(parsed)) {
|
|
1736
1869
|
throw new Error("create-windy 模板来源元数据无效");
|
|
1737
1870
|
}
|
|
1738
1871
|
return parsed;
|
|
1739
1872
|
}
|
|
1740
1873
|
async function readGenerationProvenance(targetDirectory) {
|
|
1741
|
-
const path =
|
|
1742
|
-
const parsed = JSON.parse(await
|
|
1874
|
+
const path = join8(targetDirectory, generationMetadataFile);
|
|
1875
|
+
const parsed = JSON.parse(await readFile8(path, "utf8"));
|
|
1743
1876
|
if (!isGenerationProvenance(parsed)) {
|
|
1744
1877
|
throw new Error("当前目录不是受支持的 Windy 项目:Project Lock 无效");
|
|
1745
1878
|
}
|
|
@@ -1771,9 +1904,9 @@ async function writeGenerationProvenance(targetDirectory, metadata, profile, sel
|
|
|
1771
1904
|
includeOxc
|
|
1772
1905
|
}
|
|
1773
1906
|
};
|
|
1774
|
-
const path =
|
|
1775
|
-
await mkdir2(
|
|
1776
|
-
await
|
|
1907
|
+
const path = join8(targetDirectory, generationMetadataFile);
|
|
1908
|
+
await mkdir2(join8(path, ".."), { recursive: true });
|
|
1909
|
+
await writeFile7(path, `${JSON.stringify(provenance, null, 2)}
|
|
1777
1910
|
`);
|
|
1778
1911
|
}
|
|
1779
1912
|
function isTemplateMetadata(value) {
|
|
@@ -1798,8 +1931,8 @@ function isModuleVersion(value) {
|
|
|
1798
1931
|
}
|
|
1799
1932
|
|
|
1800
1933
|
// src/registry-dependencies.ts
|
|
1801
|
-
import { lstat as lstat3, readFile as
|
|
1802
|
-
import { basename as basename2, extname as extname2, join as
|
|
1934
|
+
import { lstat as lstat3, readFile as readFile9, readdir as readdir4, writeFile as writeFile8 } from "node:fs/promises";
|
|
1935
|
+
import { basename as basename2, extname as extname2, join as join9 } from "node:path";
|
|
1803
1936
|
var dependencySections = [
|
|
1804
1937
|
"dependencies",
|
|
1805
1938
|
"devDependencies",
|
|
@@ -1827,14 +1960,14 @@ async function materializeRegistryDependencies(root, dependencies) {
|
|
|
1827
1960
|
await rewritePackageJson(path, dependencies);
|
|
1828
1961
|
continue;
|
|
1829
1962
|
}
|
|
1830
|
-
const source = await
|
|
1963
|
+
const source = await readFile9(path, "utf8");
|
|
1831
1964
|
const output = dependencies.reduce((value, dependency) => value.replaceAll(dependency.sourceName, dependency.name), source);
|
|
1832
1965
|
if (output !== source)
|
|
1833
|
-
await
|
|
1966
|
+
await writeFile8(path, output);
|
|
1834
1967
|
}
|
|
1835
1968
|
}
|
|
1836
1969
|
async function rewritePackageJson(path, registryDependencies) {
|
|
1837
|
-
const value = JSON.parse(await
|
|
1970
|
+
const value = JSON.parse(await readFile9(path, "utf8"));
|
|
1838
1971
|
let changed = false;
|
|
1839
1972
|
for (const section of dependencySections) {
|
|
1840
1973
|
const dependencies = value[section];
|
|
@@ -1849,7 +1982,7 @@ async function rewritePackageJson(path, registryDependencies) {
|
|
|
1849
1982
|
}
|
|
1850
1983
|
}
|
|
1851
1984
|
if (changed)
|
|
1852
|
-
await
|
|
1985
|
+
await writeFile8(path, `${JSON.stringify(value, null, 2)}
|
|
1853
1986
|
`);
|
|
1854
1987
|
}
|
|
1855
1988
|
async function collectFiles3(root) {
|
|
@@ -1857,10 +1990,10 @@ async function collectFiles3(root) {
|
|
|
1857
1990
|
await visit(root);
|
|
1858
1991
|
return files.sort();
|
|
1859
1992
|
async function visit(directory) {
|
|
1860
|
-
for (const name of (await
|
|
1993
|
+
for (const name of (await readdir4(directory)).sort()) {
|
|
1861
1994
|
if ([".git", "node_modules", "dist"].includes(name))
|
|
1862
1995
|
continue;
|
|
1863
|
-
const path =
|
|
1996
|
+
const path = join9(directory, name);
|
|
1864
1997
|
const metadata = await lstat3(path);
|
|
1865
1998
|
if (metadata.isSymbolicLink())
|
|
1866
1999
|
continue;
|
|
@@ -1884,39 +2017,39 @@ async function generateProject(input) {
|
|
|
1884
2017
|
const selectedModules = input.selectedModules ?? templateMetadata.modules.filter(({ name }) => requestedIncludeExample || name !== "work-order").map(({ name }) => name);
|
|
1885
2018
|
const includeExample = selectedModules.includes("work-order");
|
|
1886
2019
|
for (const relativePath2 of generationTemplatePaths(true, includeOxc)) {
|
|
1887
|
-
await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2),
|
|
2020
|
+
await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join10(input.targetDirectory, relativePath2));
|
|
1888
2021
|
}
|
|
1889
2022
|
await materializeSelectedModules(input.targetDirectory, selectedModules);
|
|
1890
2023
|
await materializeRegistryDependencies(input.targetDirectory, selectedModuleManifests(selectedModules).flatMap(({ registryDependencies }) => registryDependencies));
|
|
1891
|
-
const packagePath =
|
|
1892
|
-
const packageJson = JSON.parse(await
|
|
1893
|
-
await
|
|
2024
|
+
const packagePath = join10(input.targetDirectory, "package.json");
|
|
2025
|
+
const packageJson = JSON.parse(await readFile10(packagePath, "utf8"));
|
|
2026
|
+
await writeFile9(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
|
|
1894
2027
|
`);
|
|
1895
|
-
await
|
|
1896
|
-
await
|
|
1897
|
-
await
|
|
1898
|
-
await
|
|
2028
|
+
await writeFile9(join10(input.targetDirectory, "docker-compose.yml"), starterCompose);
|
|
2029
|
+
await writeFile9(join10(input.targetDirectory, ".env.example"), environmentExample(includeExample));
|
|
2030
|
+
await writeFile9(join10(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
|
|
2031
|
+
await writeFile9(join10(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license")));
|
|
1899
2032
|
await reflectCodeQualityChoice(input.targetDirectory, includeOxc);
|
|
1900
2033
|
await applyWindyBaseLicense(input.targetDirectory);
|
|
1901
2034
|
await applyProjectLicense(input.targetDirectory, projectLicense, input.licenseHolder);
|
|
1902
2035
|
await prepareCustomerDockerfiles(input.targetDirectory);
|
|
1903
2036
|
await writeGenerationProvenance(input.targetDirectory, templateMetadata, input.profile ?? starterProfile.key, selectedModules, includeOxc, projectLicense);
|
|
1904
|
-
await rm3(
|
|
2037
|
+
await rm3(join10(input.targetDirectory, "bun.lock"), { force: true });
|
|
1905
2038
|
await writeManagedFilesManifest(input.targetDirectory, templateMetadata.generatorVersion);
|
|
1906
2039
|
}
|
|
1907
2040
|
async function resolveTemplateSource(sourceRoot, relativePath2) {
|
|
1908
|
-
const source =
|
|
2041
|
+
const source = join10(sourceRoot, relativePath2);
|
|
1909
2042
|
try {
|
|
1910
2043
|
await lstat4(source);
|
|
1911
2044
|
return source;
|
|
1912
2045
|
} catch (error) {
|
|
1913
|
-
if (!
|
|
2046
|
+
if (!isMissing3(error))
|
|
1914
2047
|
throw error;
|
|
1915
2048
|
}
|
|
1916
2049
|
const alias = npmTemplateAliases[relativePath2];
|
|
1917
2050
|
if (!alias)
|
|
1918
2051
|
return source;
|
|
1919
|
-
return
|
|
2052
|
+
return join10(sourceRoot, alias);
|
|
1920
2053
|
}
|
|
1921
2054
|
async function assertGeneratedProjectIsClean(targetDirectory) {
|
|
1922
2055
|
const violations = [];
|
|
@@ -1941,11 +2074,11 @@ function validateProjectName(projectName) {
|
|
|
1941
2074
|
}
|
|
1942
2075
|
async function assertTargetAvailable(targetDirectory) {
|
|
1943
2076
|
try {
|
|
1944
|
-
const entries = await
|
|
2077
|
+
const entries = await readdir5(targetDirectory);
|
|
1945
2078
|
if (entries.length)
|
|
1946
2079
|
throw new Error("目标目录不是空目录");
|
|
1947
2080
|
} catch (error) {
|
|
1948
|
-
if (
|
|
2081
|
+
if (isMissing3(error))
|
|
1949
2082
|
return;
|
|
1950
2083
|
throw error;
|
|
1951
2084
|
}
|
|
@@ -1957,26 +2090,26 @@ async function copyTree(source, target) {
|
|
|
1957
2090
|
return;
|
|
1958
2091
|
if (metadata.isDirectory()) {
|
|
1959
2092
|
await mkdir3(target, { recursive: true });
|
|
1960
|
-
for (const child of await
|
|
1961
|
-
await copyTree(
|
|
2093
|
+
for (const child of await readdir5(source)) {
|
|
2094
|
+
await copyTree(join10(source, child), join10(target, child));
|
|
1962
2095
|
}
|
|
1963
2096
|
return;
|
|
1964
2097
|
}
|
|
1965
|
-
await mkdir3(
|
|
2098
|
+
await mkdir3(join10(target, ".."), { recursive: true });
|
|
1966
2099
|
await copyFile(source, target);
|
|
1967
2100
|
}
|
|
1968
2101
|
function shouldExclude(name) {
|
|
1969
2102
|
return excludedNames.has(name) || excludedRuntimeFiles.has(name);
|
|
1970
2103
|
}
|
|
1971
2104
|
async function walk(directory, visit) {
|
|
1972
|
-
for (const name of await
|
|
1973
|
-
const path =
|
|
2105
|
+
for (const name of await readdir5(directory)) {
|
|
2106
|
+
const path = join10(directory, name);
|
|
1974
2107
|
await visit(path);
|
|
1975
2108
|
if ((await lstat4(path)).isDirectory())
|
|
1976
2109
|
await walk(path, visit);
|
|
1977
2110
|
}
|
|
1978
2111
|
}
|
|
1979
|
-
function
|
|
2112
|
+
function isMissing3(error) {
|
|
1980
2113
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1981
2114
|
}
|
|
1982
2115
|
|
|
@@ -2049,9 +2182,9 @@ async function createProject(options, dependencies = {}) {
|
|
|
2049
2182
|
}
|
|
2050
2183
|
async function inspectTarget(path) {
|
|
2051
2184
|
try {
|
|
2052
|
-
return (await
|
|
2185
|
+
return (await readdir6(path)).length === 0 ? "empty" : "non-empty";
|
|
2053
2186
|
} catch (error) {
|
|
2054
|
-
if (
|
|
2187
|
+
if (isMissing4(error))
|
|
2055
2188
|
return "missing";
|
|
2056
2189
|
throw error;
|
|
2057
2190
|
}
|
|
@@ -2073,7 +2206,7 @@ function stageError(stage, error) {
|
|
|
2073
2206
|
function sanitizeReason(reason) {
|
|
2074
2207
|
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
2208
|
}
|
|
2076
|
-
function
|
|
2209
|
+
function isMissing4(error) {
|
|
2077
2210
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
2078
2211
|
}
|
|
2079
2212
|
|
|
@@ -3821,8 +3954,8 @@ import { rm as rm8 } from "node:fs/promises";
|
|
|
3821
3954
|
import { resolve as resolve5 } from "node:path";
|
|
3822
3955
|
|
|
3823
3956
|
// src/update/doctor.ts
|
|
3824
|
-
import { readFile as
|
|
3825
|
-
import { join as
|
|
3957
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
3958
|
+
import { join as join12 } from "node:path";
|
|
3826
3959
|
|
|
3827
3960
|
// src/update/process.ts
|
|
3828
3961
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -3854,12 +3987,12 @@ import {
|
|
|
3854
3987
|
access as access2,
|
|
3855
3988
|
copyFile as copyFile2,
|
|
3856
3989
|
mkdir as mkdir5,
|
|
3857
|
-
readFile as
|
|
3990
|
+
readFile as readFile11,
|
|
3858
3991
|
rm as rm5,
|
|
3859
3992
|
rmdir,
|
|
3860
|
-
writeFile as
|
|
3993
|
+
writeFile as writeFile10
|
|
3861
3994
|
} from "node:fs/promises";
|
|
3862
|
-
import { dirname as dirname3, join as
|
|
3995
|
+
import { dirname as dirname3, join as join11, resolve as resolve4, sep as sep3 } from "node:path";
|
|
3863
3996
|
var updateStatePath = ".windy/update-state.json";
|
|
3864
3997
|
async function applyTransaction(plan) {
|
|
3865
3998
|
if (plan.conflicts.length) {
|
|
@@ -3868,21 +4001,22 @@ async function applyTransaction(plan) {
|
|
|
3868
4001
|
const changed = plan.files.filter(({ action }) => action === "create" || action === "update" || action === "delete");
|
|
3869
4002
|
for (const file of changed)
|
|
3870
4003
|
assertSafePath(file.path);
|
|
3871
|
-
const backupDirectory =
|
|
4004
|
+
const backupDirectory = join11(plan.projectDirectory, ".windy/backups", plan.id);
|
|
3872
4005
|
const protectedPaths = new Set([
|
|
3873
4006
|
...changed.map(({ path: path2 }) => path2),
|
|
3874
4007
|
".windy/generation.json",
|
|
3875
4008
|
".windy/files.json",
|
|
4009
|
+
"apps/web/Dockerfile",
|
|
3876
4010
|
"bun.lock"
|
|
3877
4011
|
]);
|
|
3878
4012
|
const backupEntries = [];
|
|
3879
4013
|
for (const path2 of protectedPaths) {
|
|
3880
|
-
const source =
|
|
4014
|
+
const source = join11(plan.projectDirectory, path2);
|
|
3881
4015
|
const existed = await exists(source);
|
|
3882
4016
|
backupEntries.push({ path: path2, existed });
|
|
3883
4017
|
if (!existed)
|
|
3884
4018
|
continue;
|
|
3885
|
-
const target =
|
|
4019
|
+
const target = join11(backupDirectory, "files", path2);
|
|
3886
4020
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3887
4021
|
await copyFile2(source, target);
|
|
3888
4022
|
}
|
|
@@ -3895,7 +4029,7 @@ async function applyTransaction(plan) {
|
|
|
3895
4029
|
await writeState(plan.projectDirectory, state);
|
|
3896
4030
|
try {
|
|
3897
4031
|
for (const file of changed) {
|
|
3898
|
-
const target =
|
|
4032
|
+
const target = join11(plan.projectDirectory, file.path);
|
|
3899
4033
|
if (file.action === "delete") {
|
|
3900
4034
|
const existed = await exists(target);
|
|
3901
4035
|
await rm5(target, { force: true });
|
|
@@ -3907,7 +4041,7 @@ async function applyTransaction(plan) {
|
|
|
3907
4041
|
if (!file.content)
|
|
3908
4042
|
throw new Error(`更新计划缺少文件内容:${file.path}`);
|
|
3909
4043
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3910
|
-
await
|
|
4044
|
+
await writeFile10(target, file.content);
|
|
3911
4045
|
}
|
|
3912
4046
|
} catch (error) {
|
|
3913
4047
|
await restoreUpdateState(plan.projectDirectory, state);
|
|
@@ -3922,7 +4056,7 @@ async function applyTransaction(plan) {
|
|
|
3922
4056
|
}
|
|
3923
4057
|
async function readUpdateState(projectDirectory) {
|
|
3924
4058
|
try {
|
|
3925
|
-
const parsed = JSON.parse(await
|
|
4059
|
+
const parsed = JSON.parse(await readFile11(join11(projectDirectory, updateStatePath), "utf8"));
|
|
3926
4060
|
if (!isUpdateState(parsed))
|
|
3927
4061
|
throw new Error("更新恢复状态无效,请人工检查 .windy 目录");
|
|
3928
4062
|
return parsed;
|
|
@@ -3935,25 +4069,25 @@ async function readUpdateState(projectDirectory) {
|
|
|
3935
4069
|
async function restoreUpdateState(projectDirectory, state) {
|
|
3936
4070
|
for (const entry of state.entries) {
|
|
3937
4071
|
assertSafePath(entry.path);
|
|
3938
|
-
const target =
|
|
4072
|
+
const target = join11(projectDirectory, entry.path);
|
|
3939
4073
|
if (!entry.existed) {
|
|
3940
4074
|
await rm5(target, { force: true });
|
|
3941
4075
|
continue;
|
|
3942
4076
|
}
|
|
3943
|
-
const backup =
|
|
4077
|
+
const backup = join11(state.backupDirectory, "files", entry.path);
|
|
3944
4078
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3945
4079
|
await copyFile2(backup, target);
|
|
3946
4080
|
}
|
|
3947
|
-
await rm5(
|
|
4081
|
+
await rm5(join11(projectDirectory, updateStatePath), { force: true });
|
|
3948
4082
|
await rm5(state.backupDirectory, { recursive: true, force: true });
|
|
3949
4083
|
}
|
|
3950
4084
|
async function clearUpdateState(projectDirectory) {
|
|
3951
|
-
await rm5(
|
|
4085
|
+
await rm5(join11(projectDirectory, updateStatePath), { force: true });
|
|
3952
4086
|
}
|
|
3953
4087
|
async function writeState(projectDirectory, state) {
|
|
3954
|
-
const path2 =
|
|
4088
|
+
const path2 = join11(projectDirectory, updateStatePath);
|
|
3955
4089
|
await mkdir5(dirname3(path2), { recursive: true });
|
|
3956
|
-
await
|
|
4090
|
+
await writeFile10(path2, `${JSON.stringify(state, null, 2)}
|
|
3957
4091
|
`);
|
|
3958
4092
|
}
|
|
3959
4093
|
async function exists(path2) {
|
|
@@ -4021,7 +4155,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
4021
4155
|
if (file.ownership === "project-owned")
|
|
4022
4156
|
continue;
|
|
4023
4157
|
try {
|
|
4024
|
-
const changed = hashContent(await
|
|
4158
|
+
const changed = hashContent(await readFile12(join12(projectDirectory, file.path))) !== file.hash;
|
|
4025
4159
|
if (!changed)
|
|
4026
4160
|
continue;
|
|
4027
4161
|
modifiedManagedFiles.push(file.path);
|
|
@@ -4070,7 +4204,8 @@ var recipes = [
|
|
|
4070
4204
|
{ id: "starter-0.2.15-to-0.2.16", from: "0.2.15", to: "0.2.16" },
|
|
4071
4205
|
{ id: "starter-0.2.16-to-0.2.17", from: "0.2.16", to: "0.2.17" },
|
|
4072
4206
|
{ 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" }
|
|
4207
|
+
{ id: "starter-0.2.18-to-0.2.19", from: "0.2.18", to: "0.2.19" },
|
|
4208
|
+
{ id: "starter-0.2.19-to-0.2.20", from: "0.2.19", to: "0.2.20" }
|
|
4074
4209
|
];
|
|
4075
4210
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
4076
4211
|
if (sourceVersion === targetVersion)
|
|
@@ -4095,19 +4230,19 @@ function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
|
4095
4230
|
}
|
|
4096
4231
|
|
|
4097
4232
|
// src/update/updater.ts
|
|
4098
|
-
import { mkdir as mkdir7, readFile as
|
|
4099
|
-
import { dirname as dirname4, join as
|
|
4233
|
+
import { mkdir as mkdir7, readFile as readFile16, rm as rm7, writeFile as writeFile12 } from "node:fs/promises";
|
|
4234
|
+
import { dirname as dirname4, join as join17 } from "node:path";
|
|
4100
4235
|
|
|
4101
4236
|
// src/update/artifacts.ts
|
|
4102
|
-
import { mkdir as mkdir6, mkdtemp, readFile as
|
|
4237
|
+
import { mkdir as mkdir6, mkdtemp, readFile as readFile13 } from "node:fs/promises";
|
|
4103
4238
|
import { tmpdir } from "node:os";
|
|
4104
|
-
import { basename as basename4, join as
|
|
4239
|
+
import { basename as basename4, join as join13 } from "node:path";
|
|
4105
4240
|
class PublishedArtifactProvider {
|
|
4106
4241
|
async prepare(projectDirectory, provenance) {
|
|
4107
|
-
const temporaryRoot = await mkdtemp(
|
|
4242
|
+
const temporaryRoot = await mkdtemp(join13(tmpdir(), "windy-update-"));
|
|
4108
4243
|
const projectName = await readProjectName2(projectDirectory);
|
|
4109
|
-
const baseDirectory =
|
|
4110
|
-
const targetDirectory =
|
|
4244
|
+
const baseDirectory = join13(temporaryRoot, "base", projectName);
|
|
4245
|
+
const targetDirectory = join13(temporaryRoot, "target", projectName);
|
|
4111
4246
|
await generatePublishedBase(temporaryRoot, projectName, provenance, baseDirectory);
|
|
4112
4247
|
await generateProject({
|
|
4113
4248
|
sourceRoot: await resolveTemplateRoot(),
|
|
@@ -4124,7 +4259,7 @@ class PublishedArtifactProvider {
|
|
|
4124
4259
|
}
|
|
4125
4260
|
}
|
|
4126
4261
|
async function generatePublishedBase(temporaryRoot, projectName, provenance, expectedDirectory) {
|
|
4127
|
-
const parent =
|
|
4262
|
+
const parent = join13(temporaryRoot, "base");
|
|
4128
4263
|
await mkdir6(parent, { recursive: true });
|
|
4129
4264
|
const args = [
|
|
4130
4265
|
"--bun",
|
|
@@ -4148,18 +4283,18 @@ async function generatePublishedBase(temporaryRoot, projectName, provenance, exp
|
|
|
4148
4283
|
}
|
|
4149
4284
|
}
|
|
4150
4285
|
await assertProcess("bunx", args, parent);
|
|
4151
|
-
if (
|
|
4286
|
+
if (join13(parent, projectName) !== expectedDirectory) {
|
|
4152
4287
|
throw new Error("历史 artifact 生成目录异常");
|
|
4153
4288
|
}
|
|
4154
4289
|
}
|
|
4155
4290
|
async function readProjectName2(projectDirectory) {
|
|
4156
|
-
const source = JSON.parse(await
|
|
4291
|
+
const source = JSON.parse(await readFile13(join13(projectDirectory, "package.json"), "utf8"));
|
|
4157
4292
|
return source.name || basename4(projectDirectory);
|
|
4158
4293
|
}
|
|
4159
4294
|
|
|
4160
4295
|
// src/update/planner.ts
|
|
4161
|
-
import { access as access3, readFile as
|
|
4162
|
-
import { join as
|
|
4296
|
+
import { access as access3, readFile as readFile15 } from "node:fs/promises";
|
|
4297
|
+
import { join as join16 } from "node:path";
|
|
4163
4298
|
|
|
4164
4299
|
// src/update/known-repairs.ts
|
|
4165
4300
|
var wrongTextVersions = new Set(["0.2.12", "0.2.13"]);
|
|
@@ -4202,19 +4337,19 @@ function normalizeTableCell(cell) {
|
|
|
4202
4337
|
}
|
|
4203
4338
|
|
|
4204
4339
|
// src/update/merge.ts
|
|
4205
|
-
import { mkdtemp as mkdtemp2, rm as rm6, writeFile as
|
|
4340
|
+
import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile11 } from "node:fs/promises";
|
|
4206
4341
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4207
|
-
import { join as
|
|
4342
|
+
import { join as join14 } from "node:path";
|
|
4208
4343
|
async function mergeTextFile(base, ours, theirs) {
|
|
4209
|
-
const root = await mkdtemp2(
|
|
4344
|
+
const root = await mkdtemp2(join14(tmpdir2(), "windy-merge-"));
|
|
4210
4345
|
try {
|
|
4211
|
-
const oursPath =
|
|
4212
|
-
const basePath =
|
|
4213
|
-
const theirsPath =
|
|
4346
|
+
const oursPath = join14(root, "ours");
|
|
4347
|
+
const basePath = join14(root, "base");
|
|
4348
|
+
const theirsPath = join14(root, "theirs");
|
|
4214
4349
|
await Promise.all([
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4350
|
+
writeFile11(oursPath, ours),
|
|
4351
|
+
writeFile11(basePath, base),
|
|
4352
|
+
writeFile11(theirsPath, theirs)
|
|
4218
4353
|
]);
|
|
4219
4354
|
const result = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
|
|
4220
4355
|
if (result.code === 0) {
|
|
@@ -4382,8 +4517,8 @@ function isTableSeparator(cell) {
|
|
|
4382
4517
|
}
|
|
4383
4518
|
|
|
4384
4519
|
// src/update/migration-reconciler.ts
|
|
4385
|
-
import { readFile as
|
|
4386
|
-
import { join as
|
|
4520
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
4521
|
+
import { join as join15 } from "node:path";
|
|
4387
4522
|
|
|
4388
4523
|
// src/update/snapshot-delta.ts
|
|
4389
4524
|
function applySnapshotDelta(before, after, current, path2, conflicts, conflictPath) {
|
|
@@ -4490,7 +4625,7 @@ async function reconcileDrizzleMigrations(input) {
|
|
|
4490
4625
|
const tag = `${prefix}_${name}`;
|
|
4491
4626
|
const sqlPath = `${drizzleRoot}/${tag}.sql`;
|
|
4492
4627
|
const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
|
|
4493
|
-
const sql = await
|
|
4628
|
+
const sql = await readFile14(join15(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
|
|
4494
4629
|
merged.id = targetAfter.id;
|
|
4495
4630
|
merged.prevId = currentSnapshot.id;
|
|
4496
4631
|
const snapshot = encodeJson(merged);
|
|
@@ -4537,13 +4672,13 @@ function unchanged(targetManifest) {
|
|
|
4537
4672
|
async function readJournals(input) {
|
|
4538
4673
|
try {
|
|
4539
4674
|
const [base, project, target] = await Promise.all([
|
|
4540
|
-
readJson(
|
|
4541
|
-
readJson(
|
|
4542
|
-
readJson(
|
|
4675
|
+
readJson(join15(input.baseDirectory, journalPath)),
|
|
4676
|
+
readJson(join15(input.projectDirectory, journalPath)),
|
|
4677
|
+
readJson(join15(input.targetDirectory, journalPath))
|
|
4543
4678
|
]);
|
|
4544
4679
|
return { base, project, target };
|
|
4545
4680
|
} catch (error) {
|
|
4546
|
-
if (
|
|
4681
|
+
if (isMissing5(error))
|
|
4547
4682
|
return;
|
|
4548
4683
|
throw error;
|
|
4549
4684
|
}
|
|
@@ -4551,7 +4686,7 @@ async function readJournals(input) {
|
|
|
4551
4686
|
async function preservePublishedHistory(manifest, projectDirectory, overrides) {
|
|
4552
4687
|
const entries = manifest.files.filter(({ path: path2 }) => isMigrationArtifact(path2) && path2 !== journalPath);
|
|
4553
4688
|
await Promise.all(entries.map(async ({ path: path2 }) => {
|
|
4554
|
-
overrides.set(path2, await
|
|
4689
|
+
overrides.set(path2, await readFile14(join15(projectDirectory, path2)));
|
|
4555
4690
|
}));
|
|
4556
4691
|
return entries;
|
|
4557
4692
|
}
|
|
@@ -4565,7 +4700,7 @@ async function readSnapshot(root, tag) {
|
|
|
4565
4700
|
const match = migrationPattern.exec(tag);
|
|
4566
4701
|
if (!match)
|
|
4567
4702
|
throw new Error(`${journalPath}:migration tag 格式无效:${tag}`);
|
|
4568
|
-
return await readJson(
|
|
4703
|
+
return await readJson(join15(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
|
|
4569
4704
|
}
|
|
4570
4705
|
function validateUniqueNames(journal, label, conflicts) {
|
|
4571
4706
|
const seen = new Set;
|
|
@@ -4606,9 +4741,9 @@ function encodeJson(value) {
|
|
|
4606
4741
|
`);
|
|
4607
4742
|
}
|
|
4608
4743
|
async function readJson(path2) {
|
|
4609
|
-
return JSON.parse(await
|
|
4744
|
+
return JSON.parse(await readFile14(path2, "utf8"));
|
|
4610
4745
|
}
|
|
4611
|
-
function
|
|
4746
|
+
function isMissing5(error) {
|
|
4612
4747
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
4613
4748
|
}
|
|
4614
4749
|
|
|
@@ -4729,9 +4864,9 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
4729
4864
|
}
|
|
4730
4865
|
async function planFile(path2, project, baseRoot, targetRoot, source, target, provenance, targetContentOverride) {
|
|
4731
4866
|
const [ours, base, theirs] = await Promise.all([
|
|
4732
|
-
readOptional(
|
|
4733
|
-
readOptional(
|
|
4734
|
-
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(
|
|
4867
|
+
readOptional(join16(project, path2)),
|
|
4868
|
+
readOptional(join16(baseRoot, path2)),
|
|
4869
|
+
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join16(targetRoot, path2))
|
|
4735
4870
|
]);
|
|
4736
4871
|
const ownership = target?.ownership || source?.ownership || classifyOwnership(path2);
|
|
4737
4872
|
const repaired = ours && theirs && provenance ? repairKnownDerivedFile({
|
|
@@ -4813,7 +4948,7 @@ function conflict(path2, ownership, reason) {
|
|
|
4813
4948
|
}
|
|
4814
4949
|
async function readOptional(path2) {
|
|
4815
4950
|
try {
|
|
4816
|
-
return await
|
|
4951
|
+
return await readFile15(path2);
|
|
4817
4952
|
} catch (error) {
|
|
4818
4953
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4819
4954
|
return;
|
|
@@ -4829,7 +4964,7 @@ async function assertGitClean(projectDirectory) {
|
|
|
4829
4964
|
}
|
|
4830
4965
|
async function assertNoPendingUpdate(projectDirectory) {
|
|
4831
4966
|
try {
|
|
4832
|
-
await access3(
|
|
4967
|
+
await access3(join16(projectDirectory, ".windy/update-state.json"));
|
|
4833
4968
|
throw new Error("检测到未完成更新,请先运行 bun run windy doctor --repair");
|
|
4834
4969
|
} catch (error) {
|
|
4835
4970
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
@@ -4863,9 +4998,17 @@ class DefaultStarterUpdater {
|
|
|
4863
4998
|
async verify(result2) {
|
|
4864
4999
|
const steps = [];
|
|
4865
5000
|
try {
|
|
5001
|
+
const dockerSync = await syncWebDockerfileInProject(result2.plan.projectDirectory, { requireAnchors: true });
|
|
5002
|
+
if (dockerSync.changed) {
|
|
5003
|
+
addChangedFile(result2, "apps/web/Dockerfile");
|
|
5004
|
+
}
|
|
5005
|
+
steps.push({
|
|
5006
|
+
name: "同步 Web Dockerfile workspace COPY 清单",
|
|
5007
|
+
status: dockerSync.synced ? "passed" : "skipped"
|
|
5008
|
+
});
|
|
4866
5009
|
await this.#execute("bun", ["install", "--force"], result2.plan.projectDirectory);
|
|
4867
5010
|
steps.push({ name: "bun install --force", status: "passed" });
|
|
4868
|
-
const packageJson = JSON.parse(await
|
|
5011
|
+
const packageJson = JSON.parse(await readFile16(join17(result2.plan.projectDirectory, "package.json"), "utf8"));
|
|
4869
5012
|
for (const name of ["lint", "typecheck", "test", "build"]) {
|
|
4870
5013
|
if (!packageJson.scripts?.[name]) {
|
|
4871
5014
|
steps.push({ name: `bun run ${name}`, status: "skipped" });
|
|
@@ -4896,12 +5039,12 @@ async function finalize(result2) {
|
|
|
4896
5039
|
const root = result2.plan.projectDirectory;
|
|
4897
5040
|
const generationContent = `${JSON.stringify(result2.plan.targetProvenance, null, 2)}
|
|
4898
5041
|
`;
|
|
4899
|
-
await
|
|
5042
|
+
await writeFile12(join17(root, ".windy/generation.json"), generationContent);
|
|
4900
5043
|
const refreshedPaths = new Set([
|
|
4901
5044
|
".windy/generation.json",
|
|
4902
|
-
...result2.
|
|
5045
|
+
...result2.changedFiles
|
|
4903
5046
|
]);
|
|
4904
|
-
const refreshedHashes = new Map(await Promise.all([...refreshedPaths].map(async (path2) => [path2, hashContent(await
|
|
5047
|
+
const refreshedHashes = new Map(await Promise.all([...refreshedPaths].map(async (path2) => [path2, hashContent(await readFile16(join17(root, path2)))])));
|
|
4905
5048
|
const targetManifest = {
|
|
4906
5049
|
...result2.plan.targetManifest,
|
|
4907
5050
|
files: result2.plan.targetManifest.files.map((entry) => ({
|
|
@@ -4909,11 +5052,15 @@ async function finalize(result2) {
|
|
|
4909
5052
|
hash: refreshedHashes.get(entry.path) ?? entry.hash
|
|
4910
5053
|
}))
|
|
4911
5054
|
};
|
|
4912
|
-
await
|
|
5055
|
+
await writeFile12(join17(root, ".windy/files.json"), `${JSON.stringify(targetManifest, null, 2)}
|
|
4913
5056
|
`);
|
|
4914
5057
|
}
|
|
5058
|
+
function addChangedFile(result2, path2) {
|
|
5059
|
+
if (!result2.changedFiles.includes(path2))
|
|
5060
|
+
result2.changedFiles.push(path2);
|
|
5061
|
+
}
|
|
4915
5062
|
async function writeReport(result2, steps, relativePath2) {
|
|
4916
|
-
const path2 =
|
|
5063
|
+
const path2 = join17(result2.plan.projectDirectory, relativePath2);
|
|
4917
5064
|
await mkdir7(dirname4(path2), { recursive: true });
|
|
4918
5065
|
const lines = [
|
|
4919
5066
|
`# Windy 更新报告 ${result2.plan.id}`,
|
|
@@ -4931,16 +5078,27 @@ async function writeReport(result2, steps, relativePath2) {
|
|
|
4931
5078
|
`备份保留在 \`.windy/backups/${result2.plan.id}\`,确认稳定后可人工删除。`,
|
|
4932
5079
|
""
|
|
4933
5080
|
];
|
|
4934
|
-
await
|
|
5081
|
+
await writeFile12(path2, lines.join(`
|
|
4935
5082
|
`));
|
|
4936
5083
|
}
|
|
4937
5084
|
|
|
4938
5085
|
// src/update/cli.ts
|
|
4939
5086
|
async function runLifecycleCommand(args) {
|
|
4940
5087
|
const command = args[0];
|
|
4941
|
-
if (command !== "doctor" && command !== "update")
|
|
5088
|
+
if (command !== "doctor" && command !== "update" && command !== "sync-docker") {
|
|
4942
5089
|
return false;
|
|
5090
|
+
}
|
|
4943
5091
|
const projectDirectory = resolve5(readValue2(args, "--cwd") || process.cwd());
|
|
5092
|
+
if (command === "sync-docker") {
|
|
5093
|
+
const result2 = await syncWebDockerfileInProject(projectDirectory, {
|
|
5094
|
+
requireAnchors: true
|
|
5095
|
+
});
|
|
5096
|
+
if (!result2.synced) {
|
|
5097
|
+
throw new Error("项目缺少 apps/web/Dockerfile,无法同步 COPY 清单");
|
|
5098
|
+
}
|
|
5099
|
+
console.log(result2.changed ? "已同步 apps/web/Dockerfile workspace COPY 清单" : "apps/web/Dockerfile workspace COPY 清单已是最新");
|
|
5100
|
+
return true;
|
|
5101
|
+
}
|
|
4944
5102
|
if (command === "doctor") {
|
|
4945
5103
|
const report = await diagnoseProject(projectDirectory, args.includes("--repair"));
|
|
4946
5104
|
printDoctor(report);
|