create-windy 0.2.5 → 0.2.7
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 +13 -0
- package/dist/cli.js +179 -105
- package/package.json +1 -1
- package/template/.dockerignore +1 -0
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +1 -0
- package/template/README.md +23 -7
- package/template/apps/server/src/auth/password.ts +2 -4
- package/template/apps/server/src/persistence.ts +2 -0
- package/template/apps/web/src/components/auth/PasswordChangeForm.vue +52 -7
- package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +43 -0
- package/template/apps/web/src/composables/usePlatformSettings.ts +3 -0
- package/template/apps/web/src/pages/ServiceUnavailablePage.vue +38 -0
- package/template/apps/web/src/pages/system/resource-config.ts +2 -0
- package/template/apps/web/src/router/auth-guard.webtest.ts +146 -0
- package/template/apps/web/src/router/auth-navigation.webtest.ts +14 -0
- package/template/apps/web/src/router/index.ts +25 -11
- package/template/apps/web/src/router/index.webtest.ts +26 -209
- package/template/apps/web/src/router/manifest-routes.webtest.ts +49 -0
- package/template/apps/web/src/router/router-test-fixtures.ts +75 -0
- package/template/package.json +1 -1
- package/template/packages/database/src/schema/governance.ts +27 -22
- package/template/packages/database/src/schema/platform-settings.ts +31 -17
- package/template/packages/modules/src/system-admin-routes.ts +2 -0
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/src/password.ts +4 -0
package/dist/cli.js
CHANGED
|
@@ -852,21 +852,68 @@ function compactReason(error) {
|
|
|
852
852
|
return message ? `(原因:${message})` : "";
|
|
853
853
|
}
|
|
854
854
|
|
|
855
|
+
// src/local-environment.ts
|
|
856
|
+
import { randomBytes } from "node:crypto";
|
|
857
|
+
import { open, readFile } from "node:fs/promises";
|
|
858
|
+
import { join } from "node:path";
|
|
859
|
+
async function initializeLocalEnvironment(projectRoot, generateSecret = defaultSecret) {
|
|
860
|
+
const target = join(projectRoot, ".env");
|
|
861
|
+
const [example, gitignore] = await Promise.all([
|
|
862
|
+
readFile(join(projectRoot, ".env.example"), "utf8"),
|
|
863
|
+
readFile(join(projectRoot, ".gitignore"), "utf8")
|
|
864
|
+
]);
|
|
865
|
+
if (!gitignore.split(/\r?\n/).some((line) => line.trim() === ".env")) {
|
|
866
|
+
throw new Error("生成项目的 .gitignore 未排除 .env,拒绝写入本地密码");
|
|
867
|
+
}
|
|
868
|
+
const content = setEmptyVariable(setEmptyVariable(example, "POSTGRES_PASSWORD", generateSecret()), "WINDY_BOOTSTRAP_ADMIN_PASSWORD", generateSecret());
|
|
869
|
+
let handle;
|
|
870
|
+
try {
|
|
871
|
+
handle = await open(target, "wx", 384);
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (isAlreadyExists(error))
|
|
874
|
+
return "existing";
|
|
875
|
+
throw error;
|
|
876
|
+
}
|
|
877
|
+
try {
|
|
878
|
+
await handle.writeFile(content);
|
|
879
|
+
await handle.chmod(384);
|
|
880
|
+
} finally {
|
|
881
|
+
await handle.close();
|
|
882
|
+
}
|
|
883
|
+
return "created";
|
|
884
|
+
}
|
|
885
|
+
function setEmptyVariable(source, key, value) {
|
|
886
|
+
if (value.length < 12 || /\s|=/.test(value)) {
|
|
887
|
+
throw new Error(`${key} 的随机值不满足本地密码安全要求`);
|
|
888
|
+
}
|
|
889
|
+
const pattern = new RegExp(`^${key}=[ \\t\\r]*$`, "m");
|
|
890
|
+
if (!pattern.test(source)) {
|
|
891
|
+
throw new Error(`.env.example 缺少空的 ${key}`);
|
|
892
|
+
}
|
|
893
|
+
return source.replace(pattern, `${key}=${value}`);
|
|
894
|
+
}
|
|
895
|
+
function defaultSecret() {
|
|
896
|
+
return randomBytes(24).toString("base64url");
|
|
897
|
+
}
|
|
898
|
+
function isAlreadyExists(error) {
|
|
899
|
+
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
900
|
+
}
|
|
901
|
+
|
|
855
902
|
// src/generator.ts
|
|
856
903
|
import {
|
|
857
904
|
copyFile,
|
|
858
905
|
lstat as lstat3,
|
|
859
906
|
mkdir as mkdir3,
|
|
860
|
-
readFile as
|
|
907
|
+
readFile as readFile8,
|
|
861
908
|
readdir as readdir3,
|
|
862
909
|
rm as rm3,
|
|
863
910
|
writeFile as writeFile7
|
|
864
911
|
} from "node:fs/promises";
|
|
865
|
-
import { basename as basename2, extname as extname2, join as
|
|
912
|
+
import { basename as basename2, extname as extname2, join as join8 } from "node:path";
|
|
866
913
|
|
|
867
914
|
// src/customer-docker.ts
|
|
868
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
869
|
-
import { join } from "node:path";
|
|
915
|
+
import { readFile as readFile2, writeFile } from "node:fs/promises";
|
|
916
|
+
import { join as join2 } from "node:path";
|
|
870
917
|
var supplierWorkspaceCopies = [
|
|
871
918
|
"COPY apps/license/package.json apps/license/package.json",
|
|
872
919
|
"COPY apps/signing-service/package.json apps/signing-service/package.json",
|
|
@@ -874,8 +921,8 @@ var supplierWorkspaceCopies = [
|
|
|
874
921
|
];
|
|
875
922
|
async function prepareCustomerDockerfiles(root) {
|
|
876
923
|
await Promise.all(["apps/server/Dockerfile", "apps/web/Dockerfile"].map(async (path) => {
|
|
877
|
-
const absolutePath =
|
|
878
|
-
const source = await
|
|
924
|
+
const absolutePath = join2(root, path);
|
|
925
|
+
const source = await readFile2(absolutePath, "utf8");
|
|
879
926
|
await writeFile(absolutePath, rewriteCustomerDockerfile(source));
|
|
880
927
|
}));
|
|
881
928
|
}
|
|
@@ -889,8 +936,8 @@ function rewriteCustomerDockerfile(source) {
|
|
|
889
936
|
}
|
|
890
937
|
|
|
891
938
|
// src/code-quality.ts
|
|
892
|
-
import { readFile as
|
|
893
|
-
import { join as
|
|
939
|
+
import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
940
|
+
import { join as join3 } from "node:path";
|
|
894
941
|
var oxcStarterPolicy = [
|
|
895
942
|
"- 生成时默认预装 Oxlint、Oxfmt、lint-staged 与 Husky。",
|
|
896
943
|
"- 提交前自动格式化暂存文件;lint、typecheck 和 test 保留为显式命令。",
|
|
@@ -899,8 +946,8 @@ var oxcStarterPolicy = [
|
|
|
899
946
|
`);
|
|
900
947
|
var noCodeQualityPolicy = "- 本项目生成时未引入 lint 或 format 工具;项目所有者可按团队需要自行选择。";
|
|
901
948
|
async function reflectCodeQualityChoice(targetDirectory, includeOxc) {
|
|
902
|
-
const path =
|
|
903
|
-
const source = await
|
|
949
|
+
const path = join3(targetDirectory, "AGENTS.md");
|
|
950
|
+
const source = await readFile3(path, "utf8");
|
|
904
951
|
if (includeOxc)
|
|
905
952
|
return;
|
|
906
953
|
if (!source.includes(oxcStarterPolicy)) {
|
|
@@ -910,19 +957,19 @@ async function reflectCodeQualityChoice(targetDirectory, includeOxc) {
|
|
|
910
957
|
}
|
|
911
958
|
|
|
912
959
|
// src/license-files.ts
|
|
913
|
-
import { mkdir, readFile as
|
|
914
|
-
import { dirname, join as
|
|
960
|
+
import { mkdir, readFile as readFile4, rm, writeFile as writeFile3 } from "node:fs/promises";
|
|
961
|
+
import { dirname, join as join4, resolve as resolve2 } from "node:path";
|
|
915
962
|
import { fileURLToPath } from "node:url";
|
|
916
963
|
var packageRoot = resolve2(dirname(fileURLToPath(import.meta.url)), "..");
|
|
917
964
|
var apacheLicensePath = resolve2(packageRoot, "LICENSE");
|
|
918
965
|
var windyLicensePath = ".windy/licenses/WINDY-APACHE-2.0.txt";
|
|
919
966
|
async function applyWindyBaseLicense(targetDirectory) {
|
|
920
|
-
const target =
|
|
967
|
+
const target = join4(targetDirectory, windyLicensePath);
|
|
921
968
|
await mkdir(dirname(target), { recursive: true });
|
|
922
|
-
await writeFile3(target, await
|
|
969
|
+
await writeFile3(target, await readFile4(apacheLicensePath));
|
|
923
970
|
}
|
|
924
971
|
async function applyProjectLicense(targetDirectory, license, holder, year = new Date().getFullYear()) {
|
|
925
|
-
const target =
|
|
972
|
+
const target = join4(targetDirectory, "LICENSE");
|
|
926
973
|
if (license === "UNLICENSED") {
|
|
927
974
|
await rm(target, { force: true });
|
|
928
975
|
return;
|
|
@@ -936,7 +983,7 @@ async function applyProjectLicense(targetDirectory, license, holder, year = new
|
|
|
936
983
|
return;
|
|
937
984
|
}
|
|
938
985
|
await mkdir(targetDirectory, { recursive: true });
|
|
939
|
-
await writeFile3(target, await
|
|
986
|
+
await writeFile3(target, await readFile4(apacheLicensePath));
|
|
940
987
|
}
|
|
941
988
|
function mitLicense(year, holder) {
|
|
942
989
|
return `MIT License
|
|
@@ -965,8 +1012,8 @@ SOFTWARE.
|
|
|
965
1012
|
|
|
966
1013
|
// src/managed-files.ts
|
|
967
1014
|
import { createHash } from "node:crypto";
|
|
968
|
-
import { lstat, readdir, readFile as
|
|
969
|
-
import { join as
|
|
1015
|
+
import { lstat, readdir, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
1016
|
+
import { join as join5, relative, sep } from "node:path";
|
|
970
1017
|
var managedFilesManifestPath = ".windy/files.json";
|
|
971
1018
|
var ignoredDirectoryNames = new Set([".git", "node_modules", "dist"]);
|
|
972
1019
|
var ignoredFiles = new Set([
|
|
@@ -990,7 +1037,7 @@ var sharedScaffoldRoots = [
|
|
|
990
1037
|
async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
|
|
991
1038
|
const paths = await collectFiles(targetDirectory);
|
|
992
1039
|
const files = await Promise.all(paths.map(async (path) => {
|
|
993
|
-
const content = await
|
|
1040
|
+
const content = await readFile5(join5(targetDirectory, path));
|
|
994
1041
|
return {
|
|
995
1042
|
path,
|
|
996
1043
|
ownership: classifyOwnership(path),
|
|
@@ -1003,11 +1050,11 @@ async function writeManagedFilesManifest(targetDirectory, generatorVersion) {
|
|
|
1003
1050
|
algorithm: "sha256",
|
|
1004
1051
|
files
|
|
1005
1052
|
};
|
|
1006
|
-
await writeFile4(
|
|
1053
|
+
await writeFile4(join5(targetDirectory, managedFilesManifestPath), `${JSON.stringify(manifest, null, 2)}
|
|
1007
1054
|
`);
|
|
1008
1055
|
}
|
|
1009
1056
|
async function readManagedFilesManifest(targetDirectory) {
|
|
1010
|
-
const parsed = JSON.parse(await
|
|
1057
|
+
const parsed = JSON.parse(await readFile5(join5(targetDirectory, managedFilesManifestPath), "utf8"));
|
|
1011
1058
|
if (!isManagedFilesManifest(parsed)) {
|
|
1012
1059
|
throw new Error("受管文件清单无效,无法安全更新");
|
|
1013
1060
|
}
|
|
@@ -1031,7 +1078,7 @@ async function collectFiles(root) {
|
|
|
1031
1078
|
for (const name of (await readdir(directory)).sort()) {
|
|
1032
1079
|
if (ignoredDirectoryNames.has(name))
|
|
1033
1080
|
continue;
|
|
1034
|
-
const path =
|
|
1081
|
+
const path = join5(directory, name);
|
|
1035
1082
|
const metadata = await lstat(path);
|
|
1036
1083
|
if (metadata.isSymbolicLink())
|
|
1037
1084
|
continue;
|
|
@@ -1065,8 +1112,8 @@ function isManagedFilesManifest(value) {
|
|
|
1065
1112
|
}
|
|
1066
1113
|
|
|
1067
1114
|
// src/module-materializer.ts
|
|
1068
|
-
import { lstat as lstat2, readFile as
|
|
1069
|
-
import { basename, extname, join as
|
|
1115
|
+
import { lstat as lstat2, readFile as readFile6, readdir as readdir2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
|
|
1116
|
+
import { basename, extname, join as join6, relative as relative2, sep as sep2 } from "node:path";
|
|
1070
1117
|
var textExtensions = new Set([
|
|
1071
1118
|
".css",
|
|
1072
1119
|
".html",
|
|
@@ -1095,14 +1142,14 @@ async function materializeSelectedModules(targetDirectory, selectedNames) {
|
|
|
1095
1142
|
...manifest.documentation,
|
|
1096
1143
|
...manifest.schemaFiles
|
|
1097
1144
|
]);
|
|
1098
|
-
await Promise.all([...new Set(removedRoots)].map((path) => rm2(
|
|
1145
|
+
await Promise.all([...new Set(removedRoots)].map((path) => rm2(join6(targetDirectory, path), { recursive: true, force: true })));
|
|
1099
1146
|
await pruneWorkspaceReferences(targetDirectory, removedPackageNames, removedRoots);
|
|
1100
1147
|
}
|
|
1101
1148
|
async function stripModuleBlocks(root, selected) {
|
|
1102
1149
|
for (const path of await collectFiles2(root)) {
|
|
1103
1150
|
if (!textExtensions.has(extname(path)))
|
|
1104
1151
|
continue;
|
|
1105
|
-
const source = await
|
|
1152
|
+
const source = await readFile6(path, "utf8");
|
|
1106
1153
|
if (!source.includes("@windy-module"))
|
|
1107
1154
|
continue;
|
|
1108
1155
|
const output = stripTaggedSource(source, selected, relativePath(root, path));
|
|
@@ -1139,7 +1186,7 @@ async function readRemovedPackageNames(root, roots) {
|
|
|
1139
1186
|
const names = new Set;
|
|
1140
1187
|
for (const packageRoot2 of roots) {
|
|
1141
1188
|
try {
|
|
1142
|
-
const value = JSON.parse(await
|
|
1189
|
+
const value = JSON.parse(await readFile6(join6(root, packageRoot2, "package.json"), "utf8"));
|
|
1143
1190
|
if (value.name)
|
|
1144
1191
|
names.add(value.name);
|
|
1145
1192
|
} catch (error) {
|
|
@@ -1152,7 +1199,7 @@ async function readRemovedPackageNames(root, roots) {
|
|
|
1152
1199
|
async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
|
|
1153
1200
|
for (const path of await collectFiles2(root)) {
|
|
1154
1201
|
if (basename(path) === "package.json") {
|
|
1155
|
-
const value = JSON.parse(await
|
|
1202
|
+
const value = JSON.parse(await readFile6(path, "utf8"));
|
|
1156
1203
|
let changed = false;
|
|
1157
1204
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
1158
1205
|
const dependencies = value[section];
|
|
@@ -1170,7 +1217,7 @@ async function pruneWorkspaceReferences(root, removedPackages, removedRoots) {
|
|
|
1170
1217
|
}
|
|
1171
1218
|
if (basename(path) !== "Dockerfile")
|
|
1172
1219
|
continue;
|
|
1173
|
-
const source = await
|
|
1220
|
+
const source = await readFile6(path, "utf8");
|
|
1174
1221
|
const output = source.split(`
|
|
1175
1222
|
`).filter((line) => !removedRoots.some((root2) => line.includes(root2))).join(`
|
|
1176
1223
|
`);
|
|
@@ -1182,11 +1229,11 @@ async function pruneDatabaseArtifacts(root, tableNames, statementTokens) {
|
|
|
1182
1229
|
if (!tableNames.length && !statementTokens.length)
|
|
1183
1230
|
return;
|
|
1184
1231
|
const tablePatterns = tableNames.map((name) => new RegExp(`(?:"${escapeRegExp(name)}"|\\b${escapeRegExp(name)}\\b)`));
|
|
1185
|
-
const drizzleRoot =
|
|
1232
|
+
const drizzleRoot = join6(root, "packages/database/drizzle");
|
|
1186
1233
|
try {
|
|
1187
1234
|
for (const path of await collectFiles2(drizzleRoot)) {
|
|
1188
1235
|
if (path.endsWith(".sql")) {
|
|
1189
|
-
const source = await
|
|
1236
|
+
const source = await readFile6(path, "utf8");
|
|
1190
1237
|
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)));
|
|
1191
1238
|
await writeFile5(path, statements.length ? `${statements.join(`
|
|
1192
1239
|
--> statement-breakpoint
|
|
@@ -1194,7 +1241,7 @@ async function pruneDatabaseArtifacts(root, tableNames, statementTokens) {
|
|
|
1194
1241
|
` : `-- 此 migration 的模块未被当前项目选择。
|
|
1195
1242
|
`);
|
|
1196
1243
|
} else if (path.endsWith("_snapshot.json")) {
|
|
1197
|
-
const snapshot = JSON.parse(await
|
|
1244
|
+
const snapshot = JSON.parse(await readFile6(path, "utf8"));
|
|
1198
1245
|
for (const name of tableNames)
|
|
1199
1246
|
delete snapshot.tables?.[`public.${name}`];
|
|
1200
1247
|
await writeFile5(path, `${JSON.stringify(snapshot, null, 2)}
|
|
@@ -1214,7 +1261,7 @@ async function collectFiles2(root) {
|
|
|
1214
1261
|
for (const name of (await readdir2(directory)).sort()) {
|
|
1215
1262
|
if ([".git", "node_modules", "dist"].includes(name))
|
|
1216
1263
|
continue;
|
|
1217
|
-
const path =
|
|
1264
|
+
const path = join6(directory, name);
|
|
1218
1265
|
const metadata = await lstat2(path);
|
|
1219
1266
|
if (metadata.isSymbolicLink())
|
|
1220
1267
|
continue;
|
|
@@ -1363,32 +1410,47 @@ ${includeExample ? "创建时包含了 `work-order` 示例模块,用于演示
|
|
|
1363
1410
|
不要使用 npm、Yarn 或 pnpm 安装本项目依赖,也不要提交它们产生的 lockfile;唯一的
|
|
1364
1411
|
依赖锁文件是 \`bun.lock\`。
|
|
1365
1412
|
|
|
1366
|
-
##
|
|
1413
|
+
## 第一次运行:完整开发环境
|
|
1367
1414
|
|
|
1368
1415
|
\`\`\`bash
|
|
1369
1416
|
bun install
|
|
1370
|
-
|
|
1371
|
-
# 为 POSTGRES_PASSWORD 和 WINDY_BOOTSTRAP_ADMIN_PASSWORD 设置本地值
|
|
1372
|
-
docker compose up --build
|
|
1417
|
+
bun run dev
|
|
1373
1418
|
\`\`\`
|
|
1374
1419
|
|
|
1420
|
+
create-windy 已创建仅供本机使用且被 Git 忽略的 \`.env\`,其中两个开发密码均为随机值,
|
|
1421
|
+
文件权限为 \`0600\`。\`bun run dev\` 会通过 Docker Compose 等待 PostgreSQL、执行
|
|
1422
|
+
Drizzle migration,再启动 Server 与 Web;不要跳过 migration 后直接把 Server 连接到
|
|
1423
|
+
空数据库。首次启动需要本机已经安装并启动 Docker 与 Docker Compose,并可能需要下载
|
|
1424
|
+
基础镜像。
|
|
1425
|
+
|
|
1375
1426
|
| 服务 | 地址 |
|
|
1376
1427
|
| ---- | ---- |
|
|
1377
1428
|
| Web | http://localhost:9746 |
|
|
1378
1429
|
| Server 健康检查 | http://localhost:3000/api/health |
|
|
1379
1430
|
| PostgreSQL | localhost:5432 |
|
|
1380
1431
|
|
|
1381
|
-
Docker Web 固定使用 \`9746
|
|
1432
|
+
Docker Web 固定使用 \`9746\`。只启动单个应用时使用:
|
|
1382
1433
|
|
|
1383
1434
|
\`\`\`bash
|
|
1384
1435
|
bun run dev:web
|
|
1436
|
+
bun run dev:server
|
|
1385
1437
|
\`\`\`
|
|
1386
1438
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1439
|
+
\`dev:web\` 不会隐式启动后端;本地 Web 从 \`8746\` 开始探测。同一项目已经运行时会
|
|
1440
|
+
直接输出已有地址,其它服务占用时会自动使用后续端口,最多检查 100 个。实际地址始终
|
|
1441
|
+
以终端输出为准。\`dev:server\` 不会自动启动 PostgreSQL 或执行 migration;未配置
|
|
1442
|
+
\`DATABASE_URL\` 时使用内存模式。
|
|
1389
1443
|
|
|
1390
1444
|
## 页面与登录
|
|
1391
1445
|
|
|
1446
|
+
全新数据库的初始平台账号为 \`admin\`。初始密码不是所有项目共用的固定值,而是
|
|
1447
|
+
create-windy 在本机 \`.env\` 中随机生成的 \`WINDY_BOOTSTRAP_ADMIN_PASSWORD\`;该文件已
|
|
1448
|
+
被 Git 忽略,请在本机直接查看,不要把密码提交到仓库或复制到文档。首次登录会要求
|
|
1449
|
+
立即修改密码,新密码必须为 12-128 个字符,且不能与当前密码相同。
|
|
1450
|
+
|
|
1451
|
+
\`.env\` 只负责全新数据库的首次初始化。账号密码写入数据库后,重启不会用环境变量
|
|
1452
|
+
覆盖已有密码;如果已经完成首次改密,应使用修改后的密码登录。
|
|
1453
|
+
|
|
1392
1454
|
| 路径 | 访问要求 | 说明 |
|
|
1393
1455
|
| ---- | -------- | ---- |
|
|
1394
1456
|
| \`/login\` | 公开 | 登录成功后回到安全的站内目标,默认进入 \`/\` |
|
|
@@ -1425,6 +1487,7 @@ ${moduleRows}
|
|
|
1425
1487
|
## 常用命令
|
|
1426
1488
|
|
|
1427
1489
|
\`\`\`bash
|
|
1490
|
+
bun run dev
|
|
1428
1491
|
bun run dev:web
|
|
1429
1492
|
bun run dev:server
|
|
1430
1493
|
bun run typecheck
|
|
@@ -1497,7 +1560,7 @@ function createStarterPackage(source, projectName, includeOxc = true, projectLic
|
|
|
1497
1560
|
...includeOxc ? {
|
|
1498
1561
|
prepare: "git rev-parse --git-dir >/dev/null 2>&1 || exit 0; husky"
|
|
1499
1562
|
} : {},
|
|
1500
|
-
dev: "
|
|
1563
|
+
dev: "docker compose up --build",
|
|
1501
1564
|
...generatorVersion ? { windy: "create-windy" } : {},
|
|
1502
1565
|
"dev:web": "bun run --cwd apps/web dev",
|
|
1503
1566
|
"dev:server": "bun run --cwd apps/server dev",
|
|
@@ -1614,21 +1677,21 @@ var excludedRuntimeFiles = new Set([
|
|
|
1614
1677
|
]);
|
|
1615
1678
|
|
|
1616
1679
|
// src/provenance.ts
|
|
1617
|
-
import { mkdir as mkdir2, readFile as
|
|
1618
|
-
import { join as
|
|
1680
|
+
import { mkdir as mkdir2, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
|
|
1681
|
+
import { join as join7 } from "node:path";
|
|
1619
1682
|
var templateMetadataFile = ".windy-template.json";
|
|
1620
1683
|
var generationMetadataFile = ".windy/generation.json";
|
|
1621
1684
|
async function readTemplateMetadata(sourceRoot) {
|
|
1622
|
-
const path =
|
|
1623
|
-
const parsed = JSON.parse(await
|
|
1685
|
+
const path = join7(sourceRoot, templateMetadataFile);
|
|
1686
|
+
const parsed = JSON.parse(await readFile7(path, "utf8"));
|
|
1624
1687
|
if (!isTemplateMetadata(parsed)) {
|
|
1625
1688
|
throw new Error("create-windy 模板来源元数据无效");
|
|
1626
1689
|
}
|
|
1627
1690
|
return parsed;
|
|
1628
1691
|
}
|
|
1629
1692
|
async function readGenerationProvenance(targetDirectory) {
|
|
1630
|
-
const path =
|
|
1631
|
-
const parsed = JSON.parse(await
|
|
1693
|
+
const path = join7(targetDirectory, generationMetadataFile);
|
|
1694
|
+
const parsed = JSON.parse(await readFile7(path, "utf8"));
|
|
1632
1695
|
if (!isGenerationProvenance(parsed)) {
|
|
1633
1696
|
throw new Error("当前目录不是受支持的 Windy 项目:Project Lock 无效");
|
|
1634
1697
|
}
|
|
@@ -1660,8 +1723,8 @@ async function writeGenerationProvenance(targetDirectory, metadata, profile, sel
|
|
|
1660
1723
|
includeOxc
|
|
1661
1724
|
}
|
|
1662
1725
|
};
|
|
1663
|
-
const path =
|
|
1664
|
-
await mkdir2(
|
|
1726
|
+
const path = join7(targetDirectory, generationMetadataFile);
|
|
1727
|
+
await mkdir2(join7(path, ".."), { recursive: true });
|
|
1665
1728
|
await writeFile6(path, `${JSON.stringify(provenance, null, 2)}
|
|
1666
1729
|
`);
|
|
1667
1730
|
}
|
|
@@ -1697,27 +1760,27 @@ async function generateProject(input) {
|
|
|
1697
1760
|
const templateMetadata = await readTemplateMetadata(input.sourceRoot);
|
|
1698
1761
|
const selectedModules = input.selectedModules ?? templateMetadata.modules.filter(({ name }) => includeExample || name !== "work-order").map(({ name }) => name);
|
|
1699
1762
|
for (const relativePath2 of generationTemplatePaths(true, includeOxc)) {
|
|
1700
|
-
await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2),
|
|
1763
|
+
await copyTree(await resolveTemplateSource(input.sourceRoot, relativePath2), join8(input.targetDirectory, relativePath2));
|
|
1701
1764
|
}
|
|
1702
1765
|
await materializeSelectedModules(input.targetDirectory, selectedModules);
|
|
1703
|
-
const packagePath =
|
|
1704
|
-
const packageJson = JSON.parse(await
|
|
1766
|
+
const packagePath = join8(input.targetDirectory, "package.json");
|
|
1767
|
+
const packageJson = JSON.parse(await readFile8(packagePath, "utf8"));
|
|
1705
1768
|
await writeFile7(packagePath, `${JSON.stringify(createStarterPackage(packageJson, input.projectName, includeOxc, projectLicense, selectedModules, templateMetadata.generatorVersion), null, 2)}
|
|
1706
1769
|
`);
|
|
1707
|
-
await writeFile7(
|
|
1708
|
-
await writeFile7(
|
|
1709
|
-
await writeFile7(
|
|
1710
|
-
await writeFile7(
|
|
1770
|
+
await writeFile7(join8(input.targetDirectory, "docker-compose.yml"), starterCompose);
|
|
1771
|
+
await writeFile7(join8(input.targetDirectory, ".env.example"), environmentExample(includeExample));
|
|
1772
|
+
await writeFile7(join8(input.targetDirectory, "README.md"), starterReadme(input.projectName, includeExample, includeOxc, projectLicense, selectedModules));
|
|
1773
|
+
await writeFile7(join8(input.targetDirectory, "docs/README.md"), starterDocsReadme(selectedModules.includes("system.license")));
|
|
1711
1774
|
await reflectCodeQualityChoice(input.targetDirectory, includeOxc);
|
|
1712
1775
|
await applyWindyBaseLicense(input.targetDirectory);
|
|
1713
1776
|
await applyProjectLicense(input.targetDirectory, projectLicense, input.licenseHolder);
|
|
1714
1777
|
await prepareCustomerDockerfiles(input.targetDirectory);
|
|
1715
1778
|
await writeGenerationProvenance(input.targetDirectory, templateMetadata, input.profile ?? starterProfile.key, selectedModules, includeOxc, projectLicense);
|
|
1716
|
-
await rm3(
|
|
1779
|
+
await rm3(join8(input.targetDirectory, "bun.lock"), { force: true });
|
|
1717
1780
|
await writeManagedFilesManifest(input.targetDirectory, templateMetadata.generatorVersion);
|
|
1718
1781
|
}
|
|
1719
1782
|
async function resolveTemplateSource(sourceRoot, relativePath2) {
|
|
1720
|
-
const source =
|
|
1783
|
+
const source = join8(sourceRoot, relativePath2);
|
|
1721
1784
|
try {
|
|
1722
1785
|
await lstat3(source);
|
|
1723
1786
|
return source;
|
|
@@ -1728,7 +1791,7 @@ async function resolveTemplateSource(sourceRoot, relativePath2) {
|
|
|
1728
1791
|
const alias = npmTemplateAliases[relativePath2];
|
|
1729
1792
|
if (!alias)
|
|
1730
1793
|
return source;
|
|
1731
|
-
return
|
|
1794
|
+
return join8(sourceRoot, alias);
|
|
1732
1795
|
}
|
|
1733
1796
|
async function assertGeneratedProjectIsClean(targetDirectory) {
|
|
1734
1797
|
const violations = [];
|
|
@@ -1770,11 +1833,11 @@ async function copyTree(source, target) {
|
|
|
1770
1833
|
if (metadata.isDirectory()) {
|
|
1771
1834
|
await mkdir3(target, { recursive: true });
|
|
1772
1835
|
for (const child of await readdir3(source)) {
|
|
1773
|
-
await copyTree(
|
|
1836
|
+
await copyTree(join8(source, child), join8(target, child));
|
|
1774
1837
|
}
|
|
1775
1838
|
return;
|
|
1776
1839
|
}
|
|
1777
|
-
await mkdir3(
|
|
1840
|
+
await mkdir3(join8(target, ".."), { recursive: true });
|
|
1778
1841
|
await copyFile(source, target);
|
|
1779
1842
|
}
|
|
1780
1843
|
function shouldExclude(name) {
|
|
@@ -1782,7 +1845,7 @@ function shouldExclude(name) {
|
|
|
1782
1845
|
}
|
|
1783
1846
|
async function walk(directory, visit) {
|
|
1784
1847
|
for (const name of await readdir3(directory)) {
|
|
1785
|
-
const path =
|
|
1848
|
+
const path = join8(directory, name);
|
|
1786
1849
|
await visit(path);
|
|
1787
1850
|
if ((await lstat3(path)).isDirectory())
|
|
1788
1851
|
await walk(path, visit);
|
|
@@ -1815,6 +1878,7 @@ async function createProject(options, dependencies = {}) {
|
|
|
1815
1878
|
const inspect = dependencies.inspect ?? assertGeneratedProjectIsClean;
|
|
1816
1879
|
const resolveTemplate = dependencies.resolveTemplate ?? resolveTemplateRoot;
|
|
1817
1880
|
let stage = "生成文件";
|
|
1881
|
+
let localEnvironment = "existing";
|
|
1818
1882
|
try {
|
|
1819
1883
|
await generate({
|
|
1820
1884
|
sourceRoot: await resolveTemplate(),
|
|
@@ -1831,6 +1895,8 @@ async function createProject(options, dependencies = {}) {
|
|
|
1831
1895
|
await inspect(options.targetDirectory);
|
|
1832
1896
|
stage = "安装与验证";
|
|
1833
1897
|
await installAndVerify(options.targetDirectory, options.install, options.verify, options.includeOxc, dependencies.commandExecutor);
|
|
1898
|
+
stage = "初始化本地环境";
|
|
1899
|
+
localEnvironment = await (dependencies.initializeEnvironment ?? initializeLocalEnvironment)(options.targetDirectory);
|
|
1834
1900
|
} catch (error) {
|
|
1835
1901
|
await rollbackFailedTarget(options, targetState);
|
|
1836
1902
|
throw stageError(stage, error);
|
|
@@ -1848,7 +1914,13 @@ async function createProject(options, dependencies = {}) {
|
|
|
1848
1914
|
已创建 ${options.projectName}`);
|
|
1849
1915
|
log(`运行时:Bun ${bunVersion}`);
|
|
1850
1916
|
log(options.initializeGit ? gitInitialized ? "Git:已创建 main 分支和首次提交" : "Git:项目已保留,请按上述提示完成初始化" : "Git:已按 --no-git 跳过");
|
|
1851
|
-
log(
|
|
1917
|
+
log(localEnvironment === "created" ? "本地 .env:已生成随机开发密码(权限 0600)" : "本地 .env:已存在,未覆盖");
|
|
1918
|
+
log("初始平台账号:admin");
|
|
1919
|
+
log("初始平台密码:查看 .env 中的 WINDY_BOOTSTRAP_ADMIN_PASSWORD(首次登录后必须修改)");
|
|
1920
|
+
log("下一步(启动数据库、迁移、后端与前端):");
|
|
1921
|
+
log(` cd ${options.projectName}`);
|
|
1922
|
+
log(" bun run dev");
|
|
1923
|
+
log("仅启动前端:bun run dev:web");
|
|
1852
1924
|
}
|
|
1853
1925
|
async function inspectTarget(path) {
|
|
1854
1926
|
try {
|
|
@@ -3624,8 +3696,8 @@ import { rm as rm8 } from "node:fs/promises";
|
|
|
3624
3696
|
import { resolve as resolve4 } from "node:path";
|
|
3625
3697
|
|
|
3626
3698
|
// src/update/doctor.ts
|
|
3627
|
-
import { readFile as
|
|
3628
|
-
import { join as
|
|
3699
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
3700
|
+
import { join as join10 } from "node:path";
|
|
3629
3701
|
|
|
3630
3702
|
// src/update/process.ts
|
|
3631
3703
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -3657,11 +3729,11 @@ import {
|
|
|
3657
3729
|
access as access2,
|
|
3658
3730
|
copyFile as copyFile2,
|
|
3659
3731
|
mkdir as mkdir5,
|
|
3660
|
-
readFile as
|
|
3732
|
+
readFile as readFile9,
|
|
3661
3733
|
rm as rm5,
|
|
3662
3734
|
writeFile as writeFile8
|
|
3663
3735
|
} from "node:fs/promises";
|
|
3664
|
-
import { dirname as dirname3, join as
|
|
3736
|
+
import { dirname as dirname3, join as join9 } from "node:path";
|
|
3665
3737
|
var updateStatePath = ".windy/update-state.json";
|
|
3666
3738
|
async function applyTransaction(plan) {
|
|
3667
3739
|
if (plan.conflicts.length) {
|
|
@@ -3670,7 +3742,7 @@ async function applyTransaction(plan) {
|
|
|
3670
3742
|
const changed = plan.files.filter(({ action }) => action === "create" || action === "update" || action === "delete");
|
|
3671
3743
|
for (const file of changed)
|
|
3672
3744
|
assertSafePath(file.path);
|
|
3673
|
-
const backupDirectory =
|
|
3745
|
+
const backupDirectory = join9(plan.projectDirectory, ".windy/backups", plan.id);
|
|
3674
3746
|
const protectedPaths = new Set([
|
|
3675
3747
|
...changed.map(({ path: path2 }) => path2),
|
|
3676
3748
|
".windy/generation.json",
|
|
@@ -3679,12 +3751,12 @@ async function applyTransaction(plan) {
|
|
|
3679
3751
|
]);
|
|
3680
3752
|
const backupEntries = [];
|
|
3681
3753
|
for (const path2 of protectedPaths) {
|
|
3682
|
-
const source =
|
|
3754
|
+
const source = join9(plan.projectDirectory, path2);
|
|
3683
3755
|
const existed = await exists(source);
|
|
3684
3756
|
backupEntries.push({ path: path2, existed });
|
|
3685
3757
|
if (!existed)
|
|
3686
3758
|
continue;
|
|
3687
|
-
const target =
|
|
3759
|
+
const target = join9(backupDirectory, "files", path2);
|
|
3688
3760
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3689
3761
|
await copyFile2(source, target);
|
|
3690
3762
|
}
|
|
@@ -3697,7 +3769,7 @@ async function applyTransaction(plan) {
|
|
|
3697
3769
|
await writeState(plan.projectDirectory, state);
|
|
3698
3770
|
try {
|
|
3699
3771
|
for (const file of changed) {
|
|
3700
|
-
const target =
|
|
3772
|
+
const target = join9(plan.projectDirectory, file.path);
|
|
3701
3773
|
if (file.action === "delete") {
|
|
3702
3774
|
await rm5(target, { force: true });
|
|
3703
3775
|
continue;
|
|
@@ -3720,7 +3792,7 @@ async function applyTransaction(plan) {
|
|
|
3720
3792
|
}
|
|
3721
3793
|
async function readUpdateState(projectDirectory) {
|
|
3722
3794
|
try {
|
|
3723
|
-
const parsed = JSON.parse(await
|
|
3795
|
+
const parsed = JSON.parse(await readFile9(join9(projectDirectory, updateStatePath), "utf8"));
|
|
3724
3796
|
if (!isUpdateState(parsed))
|
|
3725
3797
|
throw new Error("更新恢复状态无效,请人工检查 .windy 目录");
|
|
3726
3798
|
return parsed;
|
|
@@ -3733,23 +3805,23 @@ async function readUpdateState(projectDirectory) {
|
|
|
3733
3805
|
async function restoreUpdateState(projectDirectory, state) {
|
|
3734
3806
|
for (const entry of state.entries) {
|
|
3735
3807
|
assertSafePath(entry.path);
|
|
3736
|
-
const target =
|
|
3808
|
+
const target = join9(projectDirectory, entry.path);
|
|
3737
3809
|
if (!entry.existed) {
|
|
3738
3810
|
await rm5(target, { force: true });
|
|
3739
3811
|
continue;
|
|
3740
3812
|
}
|
|
3741
|
-
const backup =
|
|
3813
|
+
const backup = join9(state.backupDirectory, "files", entry.path);
|
|
3742
3814
|
await mkdir5(dirname3(target), { recursive: true });
|
|
3743
3815
|
await copyFile2(backup, target);
|
|
3744
3816
|
}
|
|
3745
|
-
await rm5(
|
|
3817
|
+
await rm5(join9(projectDirectory, updateStatePath), { force: true });
|
|
3746
3818
|
await rm5(state.backupDirectory, { recursive: true, force: true });
|
|
3747
3819
|
}
|
|
3748
3820
|
async function clearUpdateState(projectDirectory) {
|
|
3749
|
-
await rm5(
|
|
3821
|
+
await rm5(join9(projectDirectory, updateStatePath), { force: true });
|
|
3750
3822
|
}
|
|
3751
3823
|
async function writeState(projectDirectory, state) {
|
|
3752
|
-
const path2 =
|
|
3824
|
+
const path2 = join9(projectDirectory, updateStatePath);
|
|
3753
3825
|
await mkdir5(dirname3(path2), { recursive: true });
|
|
3754
3826
|
await writeFile8(path2, `${JSON.stringify(state, null, 2)}
|
|
3755
3827
|
`);
|
|
@@ -3799,7 +3871,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
3799
3871
|
if (file.ownership === "project-owned")
|
|
3800
3872
|
continue;
|
|
3801
3873
|
try {
|
|
3802
|
-
const changed = hashContent(await
|
|
3874
|
+
const changed = hashContent(await readFile10(join10(projectDirectory, file.path))) !== file.hash;
|
|
3803
3875
|
if (!changed)
|
|
3804
3876
|
continue;
|
|
3805
3877
|
modifiedManagedFiles.push(file.path);
|
|
@@ -3834,7 +3906,9 @@ var recipes = [
|
|
|
3834
3906
|
{ id: "starter-0.2.1-to-0.2.2", from: "0.2.1", to: "0.2.2" },
|
|
3835
3907
|
{ id: "starter-0.2.2-to-0.2.3", from: "0.2.2", to: "0.2.3" },
|
|
3836
3908
|
{ id: "starter-0.2.3-to-0.2.4", from: "0.2.3", to: "0.2.4" },
|
|
3837
|
-
{ id: "starter-0.2.4-to-0.2.5", from: "0.2.4", to: "0.2.5" }
|
|
3909
|
+
{ id: "starter-0.2.4-to-0.2.5", from: "0.2.4", to: "0.2.5" },
|
|
3910
|
+
{ id: "starter-0.2.5-to-0.2.6", from: "0.2.5", to: "0.2.6" },
|
|
3911
|
+
{ id: "starter-0.2.6-to-0.2.7", from: "0.2.6", to: "0.2.7" }
|
|
3838
3912
|
];
|
|
3839
3913
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
3840
3914
|
if (sourceVersion === targetVersion)
|
|
@@ -3859,19 +3933,19 @@ function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
|
3859
3933
|
}
|
|
3860
3934
|
|
|
3861
3935
|
// src/update/updater.ts
|
|
3862
|
-
import { mkdir as mkdir7, readFile as
|
|
3863
|
-
import { dirname as dirname4, join as
|
|
3936
|
+
import { mkdir as mkdir7, readFile as readFile13, rm as rm7, writeFile as writeFile10 } from "node:fs/promises";
|
|
3937
|
+
import { dirname as dirname4, join as join14 } from "node:path";
|
|
3864
3938
|
|
|
3865
3939
|
// src/update/artifacts.ts
|
|
3866
|
-
import { mkdir as mkdir6, mkdtemp, readFile as
|
|
3940
|
+
import { mkdir as mkdir6, mkdtemp, readFile as readFile11 } from "node:fs/promises";
|
|
3867
3941
|
import { tmpdir } from "node:os";
|
|
3868
|
-
import { basename as basename3, join as
|
|
3942
|
+
import { basename as basename3, join as join11 } from "node:path";
|
|
3869
3943
|
class PublishedArtifactProvider {
|
|
3870
3944
|
async prepare(projectDirectory, provenance) {
|
|
3871
|
-
const temporaryRoot = await mkdtemp(
|
|
3945
|
+
const temporaryRoot = await mkdtemp(join11(tmpdir(), "windy-update-"));
|
|
3872
3946
|
const projectName = await readProjectName2(projectDirectory);
|
|
3873
|
-
const baseDirectory =
|
|
3874
|
-
const targetDirectory =
|
|
3947
|
+
const baseDirectory = join11(temporaryRoot, "base", projectName);
|
|
3948
|
+
const targetDirectory = join11(temporaryRoot, "target", projectName);
|
|
3875
3949
|
await generatePublishedBase(temporaryRoot, projectName, provenance, baseDirectory);
|
|
3876
3950
|
await generateProject({
|
|
3877
3951
|
sourceRoot: await resolveTemplateRoot(),
|
|
@@ -3887,7 +3961,7 @@ class PublishedArtifactProvider {
|
|
|
3887
3961
|
}
|
|
3888
3962
|
}
|
|
3889
3963
|
async function generatePublishedBase(temporaryRoot, projectName, provenance, expectedDirectory) {
|
|
3890
|
-
const parent =
|
|
3964
|
+
const parent = join11(temporaryRoot, "base");
|
|
3891
3965
|
await mkdir6(parent, { recursive: true });
|
|
3892
3966
|
const args = [
|
|
3893
3967
|
"--bun",
|
|
@@ -3911,29 +3985,29 @@ async function generatePublishedBase(temporaryRoot, projectName, provenance, exp
|
|
|
3911
3985
|
}
|
|
3912
3986
|
}
|
|
3913
3987
|
await assertProcess("bunx", args, parent);
|
|
3914
|
-
if (
|
|
3988
|
+
if (join11(parent, projectName) !== expectedDirectory) {
|
|
3915
3989
|
throw new Error("历史 artifact 生成目录异常");
|
|
3916
3990
|
}
|
|
3917
3991
|
}
|
|
3918
3992
|
async function readProjectName2(projectDirectory) {
|
|
3919
|
-
const source = JSON.parse(await
|
|
3993
|
+
const source = JSON.parse(await readFile11(join11(projectDirectory, "package.json"), "utf8"));
|
|
3920
3994
|
return source.name || basename3(projectDirectory);
|
|
3921
3995
|
}
|
|
3922
3996
|
|
|
3923
3997
|
// src/update/planner.ts
|
|
3924
|
-
import { access as access3, readFile as
|
|
3925
|
-
import { join as
|
|
3998
|
+
import { access as access3, readFile as readFile12 } from "node:fs/promises";
|
|
3999
|
+
import { join as join13 } from "node:path";
|
|
3926
4000
|
|
|
3927
4001
|
// src/update/merge.ts
|
|
3928
4002
|
import { mkdtemp as mkdtemp2, rm as rm6, writeFile as writeFile9 } from "node:fs/promises";
|
|
3929
4003
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
3930
|
-
import { join as
|
|
4004
|
+
import { join as join12 } from "node:path";
|
|
3931
4005
|
async function mergeTextFile(base, ours, theirs) {
|
|
3932
|
-
const root = await mkdtemp2(
|
|
4006
|
+
const root = await mkdtemp2(join12(tmpdir2(), "windy-merge-"));
|
|
3933
4007
|
try {
|
|
3934
|
-
const oursPath =
|
|
3935
|
-
const basePath =
|
|
3936
|
-
const theirsPath =
|
|
4008
|
+
const oursPath = join12(root, "ours");
|
|
4009
|
+
const basePath = join12(root, "base");
|
|
4010
|
+
const theirsPath = join12(root, "theirs");
|
|
3937
4011
|
await Promise.all([
|
|
3938
4012
|
writeFile9(oursPath, ours),
|
|
3939
4013
|
writeFile9(basePath, base),
|
|
@@ -4055,9 +4129,9 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
4055
4129
|
}
|
|
4056
4130
|
async function planFile(path2, project, baseRoot, targetRoot, source, target) {
|
|
4057
4131
|
const [ours, base, theirs] = await Promise.all([
|
|
4058
|
-
readOptional(
|
|
4059
|
-
readOptional(
|
|
4060
|
-
readOptional(
|
|
4132
|
+
readOptional(join13(project, path2)),
|
|
4133
|
+
readOptional(join13(baseRoot, path2)),
|
|
4134
|
+
readOptional(join13(targetRoot, path2))
|
|
4061
4135
|
]);
|
|
4062
4136
|
const ownership = target?.ownership || source?.ownership || classifyOwnership(path2);
|
|
4063
4137
|
if (path2.startsWith("packages/database/drizzle/") && path2.endsWith(".sql")) {
|
|
@@ -4125,7 +4199,7 @@ function conflict(path2, ownership, reason) {
|
|
|
4125
4199
|
}
|
|
4126
4200
|
async function readOptional(path2) {
|
|
4127
4201
|
try {
|
|
4128
|
-
return await
|
|
4202
|
+
return await readFile12(path2);
|
|
4129
4203
|
} catch (error) {
|
|
4130
4204
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
4131
4205
|
return;
|
|
@@ -4141,7 +4215,7 @@ async function assertGitClean(projectDirectory) {
|
|
|
4141
4215
|
}
|
|
4142
4216
|
async function assertNoPendingUpdate(projectDirectory) {
|
|
4143
4217
|
try {
|
|
4144
|
-
await access3(
|
|
4218
|
+
await access3(join13(projectDirectory, ".windy/update-state.json"));
|
|
4145
4219
|
throw new Error("检测到未完成更新,请先运行 bun run windy doctor --repair");
|
|
4146
4220
|
} catch (error) {
|
|
4147
4221
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
@@ -4177,7 +4251,7 @@ class DefaultStarterUpdater {
|
|
|
4177
4251
|
try {
|
|
4178
4252
|
await this.#execute("bun", ["install", "--force"], result.plan.projectDirectory);
|
|
4179
4253
|
steps.push({ name: "bun install --force", status: "passed" });
|
|
4180
|
-
const packageJson = JSON.parse(await
|
|
4254
|
+
const packageJson = JSON.parse(await readFile13(join14(result.plan.projectDirectory, "package.json"), "utf8"));
|
|
4181
4255
|
for (const name of ["lint", "typecheck", "test", "build"]) {
|
|
4182
4256
|
if (!packageJson.scripts?.[name]) {
|
|
4183
4257
|
steps.push({ name: `bun run ${name}`, status: "skipped" });
|
|
@@ -4206,13 +4280,13 @@ class DefaultStarterUpdater {
|
|
|
4206
4280
|
}
|
|
4207
4281
|
async function finalize(result) {
|
|
4208
4282
|
const root = result.plan.projectDirectory;
|
|
4209
|
-
await writeFile10(
|
|
4283
|
+
await writeFile10(join14(root, ".windy/generation.json"), `${JSON.stringify(result.plan.targetProvenance, null, 2)}
|
|
4210
4284
|
`);
|
|
4211
|
-
await writeFile10(
|
|
4285
|
+
await writeFile10(join14(root, ".windy/files.json"), `${JSON.stringify(result.plan.targetManifest, null, 2)}
|
|
4212
4286
|
`);
|
|
4213
4287
|
}
|
|
4214
4288
|
async function writeReport(result, steps, relativePath2) {
|
|
4215
|
-
const path2 =
|
|
4289
|
+
const path2 = join14(result.plan.projectDirectory, relativePath2);
|
|
4216
4290
|
await mkdir7(dirname4(path2), { recursive: true });
|
|
4217
4291
|
const lines = [
|
|
4218
4292
|
`# Windy 更新报告 ${result.plan.id}`,
|