create-windy 0.3.16 → 0.3.17
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/dist/cli.js +926 -723
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +1 -1
- package/template/README.md +3 -1
- package/template/apps/server/Dockerfile +1 -1
- package/template/apps/server/src/audit/contracts.ts +8 -0
- package/template/apps/server/src/audit/recovery-queue.test.ts +48 -1
- package/template/apps/server/src/audit/recovery-queue.ts +73 -4
- package/template/apps/server/src/audit/reliable-writer.test.ts +120 -2
- package/template/apps/server/src/audit/reliable-writer.ts +98 -34
- package/template/apps/web/Dockerfile +1 -1
- package/template/docs/platform/audit-reliability.md +33 -5
- package/template/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1915,7 +1915,7 @@ function isManagedFilesManifest(value, trustedProofs) {
|
|
|
1915
1915
|
if (typeof manifest.generatorVersion !== "string")
|
|
1916
1916
|
return false;
|
|
1917
1917
|
const generatorVersion = manifest.generatorVersion;
|
|
1918
|
-
return (manifest.schemaVersion === 1 || manifest.schemaVersion === 2 || manifest.schemaVersion === 3) && manifest.algorithm === "sha256" && Array.isArray(manifest.files) && manifest.files.every((entry) => isManagedFileEntry(entry, manifest.schemaVersion, generatorVersion, trustedProofs));
|
|
1918
|
+
return (manifest.schemaVersion === 1 || manifest.schemaVersion === 2 || manifest.schemaVersion === 3 || manifest.schemaVersion === 4) && manifest.algorithm === "sha256" && Array.isArray(manifest.files) && manifest.files.every((entry) => isManagedFileEntry(entry, manifest.schemaVersion, generatorVersion, trustedProofs));
|
|
1919
1919
|
}
|
|
1920
1920
|
function isManagedFileEntry(entry, schemaVersion, generatorVersion, trustedProofs) {
|
|
1921
1921
|
if (!entry || typeof entry !== "object")
|
|
@@ -1924,9 +1924,33 @@ function isManagedFileEntry(entry, schemaVersion, generatorVersion, trustedProof
|
|
|
1924
1924
|
const hasArtifactHash = file.artifactHash !== undefined;
|
|
1925
1925
|
const hasReconciliation = file.reconciliation !== undefined;
|
|
1926
1926
|
if (file.state === "missing") {
|
|
1927
|
-
return schemaVersion === 3 && typeof file.path === "string" && file.ownership === "project-owned" && file.hash === undefined && !hasArtifactHash && !hasReconciliation && Object.keys(file).every((key) => ["path", "ownership", "state"].includes(key));
|
|
1927
|
+
return (schemaVersion === 3 || schemaVersion === 4) && typeof file.path === "string" && file.ownership === "project-owned" && file.hash === undefined && !hasArtifactHash && !hasReconciliation && Object.keys(file).every((key) => ["path", "ownership", "state"].includes(key));
|
|
1928
1928
|
}
|
|
1929
|
-
return typeof file.path === "string" && (file.ownership === "windy-managed" || file.ownership === "shared-scaffold" || file.ownership === "project-owned") && file.state === undefined && isSha256(file.hash) && (
|
|
1929
|
+
return typeof file.path === "string" && (file.ownership === "windy-managed" || file.ownership === "shared-scaffold" || file.ownership === "project-owned") && file.state === undefined && isSha256(file.hash) && isValidLineage(file, schemaVersion, generatorVersion, trustedProofs, hasArtifactHash, hasReconciliation);
|
|
1930
|
+
}
|
|
1931
|
+
function isValidLineage(file, schemaVersion, generatorVersion, trustedProofs, hasArtifactHash, hasReconciliation) {
|
|
1932
|
+
if (schemaVersion === 1) {
|
|
1933
|
+
return !hasArtifactHash && !hasReconciliation;
|
|
1934
|
+
}
|
|
1935
|
+
if (schemaVersion === 2 || schemaVersion === 3) {
|
|
1936
|
+
return hasArtifactHash === hasReconciliation && (!hasReconciliation || isTrustedReconciliation(file, generatorVersion, trustedProofs));
|
|
1937
|
+
}
|
|
1938
|
+
if (schemaVersion !== 4)
|
|
1939
|
+
return false;
|
|
1940
|
+
if (file.ownership === "project-owned") {
|
|
1941
|
+
return !hasArtifactHash && !hasReconciliation;
|
|
1942
|
+
}
|
|
1943
|
+
if (!hasArtifactHash)
|
|
1944
|
+
return !hasReconciliation;
|
|
1945
|
+
if (!isSha256(file.artifactHash))
|
|
1946
|
+
return false;
|
|
1947
|
+
if (hasReconciliation) {
|
|
1948
|
+
return isTrustedReconciliation(file, generatorVersion, trustedProofs);
|
|
1949
|
+
}
|
|
1950
|
+
return file.artifactHash !== file.hash;
|
|
1951
|
+
}
|
|
1952
|
+
function isTrustedReconciliation(file, generatorVersion, trustedProofs) {
|
|
1953
|
+
return file.ownership !== "project-owned" && isSha256(file.artifactHash) && isReconciliation(file.reconciliation) && isReleasedReconciliation(file, file.reconciliation, generatorVersion, trustedProofs);
|
|
1930
1954
|
}
|
|
1931
1955
|
function isSha256(value) {
|
|
1932
1956
|
return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
|
|
@@ -1999,7 +2023,7 @@ async function writeManifest(targetDirectory, generatorVersion, previous) {
|
|
|
1999
2023
|
}
|
|
2000
2024
|
files.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
2001
2025
|
const manifest = {
|
|
2002
|
-
schemaVersion:
|
|
2026
|
+
schemaVersion: 4,
|
|
2003
2027
|
generatorVersion,
|
|
2004
2028
|
algorithm: "sha256",
|
|
2005
2029
|
files
|
|
@@ -2852,6 +2876,7 @@ var serverDockerfilePath = "apps/server/Dockerfile";
|
|
|
2852
2876
|
var serverEntryDirectory = "apps/server";
|
|
2853
2877
|
var depsCopyAnchor = "windy-copy:deps";
|
|
2854
2878
|
var buildCopyAnchor = "windy-copy:build";
|
|
2879
|
+
var dockerBunInstallInstruction = `RUN --mount=type=cache,id=windy-bun-1.3.14,target=/root/.bun/install/cache,sharing=shared bun -e 'const child=Bun.spawn(["bun","install","--frozen-lockfile","--no-progress"],{stdin:"inherit",stdout:"inherit",stderr:"inherit"});const heartbeat=setInterval(()=>console.log("[windy] Bun \u4F9D\u8D56\u5B89\u88C5\u4ECD\u5728\u8FDB\u884C"),15000);const exitCode=await child.exited;clearInterval(heartbeat);process.exit(exitCode)'`;
|
|
2855
2880
|
var dockerfileCopyTargets = [
|
|
2856
2881
|
{
|
|
2857
2882
|
dockerfilePath: serverDockerfilePath,
|
|
@@ -3804,7 +3829,9 @@ create-windy \u5DF2\u521B\u5EFA\u4EC5\u4F9B\u672C\u673A\u4F7F\u7528\u4E14\u88AB
|
|
|
3804
3829
|
Drizzle migration\uFF0C\u518D\u542F\u52A8 Server \u4E0E Web\uFF1B\u4E0D\u8981\u8DF3\u8FC7 migration \u540E\u76F4\u63A5\u628A Server \u8FDE\u63A5\u5230
|
|
3805
3830
|
\u7A7A\u6570\u636E\u5E93\u3002\u9996\u6B21\u542F\u52A8\u9700\u8981\u672C\u673A\u5DF2\u7ECF\u5B89\u88C5\u5E76\u542F\u52A8 Docker \u4E0E Docker Compose\uFF0C\u5E76\u53EF\u80FD\u9700\u8981\u4E0B\u8F7D
|
|
3806
3831
|
\u57FA\u7840\u955C\u50CF\u3002\u540E\u7EED \`bun run dev\` \u4F1A\u590D\u7528\u5DF2\u6709\u955C\u50CF\uFF0C\u4E0D\u4F1A\u91CD\u590D\u6784\u5EFA\uFF1B\u4F9D\u8D56\u3001Dockerfile \u6216
|
|
3807
|
-
\`bun.lock\` \u53D8\u5316\u540E\u8FD0\u884C \`bun run dev:build\`\u3002
|
|
3832
|
+
\`bun.lock\` \u53D8\u5316\u540E\u8FD0\u884C \`bun run dev:build\`\u3002\u91CD\u5EFA\u4F1A\u8BA9 Web \u4E0E Server \u5E76\u53D1\u590D\u7528
|
|
3833
|
+
BuildKit \u4E2D\u7684 Bun \u5305\u4E0B\u8F7D\u7F13\u5B58\uFF0C\u53EA\u4E0B\u8F7D\u65B0\u589E\u6216\u53D1\u751F\u53D8\u5316\u7684\u4F9D\u8D56\uFF1B\u51B7\u5B89\u88C5\u6BCF 15 \u79D2\u8F93\u51FA\u4E00\u6B21
|
|
3834
|
+
\u4E0D\u542B registry \u5730\u5740\u6216\u51ED\u636E\u7684\u8FDB\u5EA6\u5FC3\u8DF3\u3002
|
|
3808
3835
|
|
|
3809
3836
|
| \u670D\u52A1 | \u5730\u5740 |
|
|
3810
3837
|
| ---- | ---- |
|
|
@@ -4011,7 +4038,8 @@ create-windy \u9ED8\u8BA4\u5EFA\u7ACB \`main\` \u5206\u652F\u5E76\u521B\u5EFA \`
|
|
|
4011
4038
|
|
|
4012
4039
|
// src/project-package.ts
|
|
4013
4040
|
var defaultLintStaged = {
|
|
4014
|
-
"*.{
|
|
4041
|
+
"*.{css,html,json,jsonc,less,md,mdx,scss,yaml,yml}": "oxfmt --write",
|
|
4042
|
+
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx,vue}": ["oxfmt --write", "oxlint"]
|
|
4015
4043
|
};
|
|
4016
4044
|
function createStarterPackage(source, projectName, includeOxc = true, projectLicense = "UNLICENSED", selectedModules = [], generatorVersion) {
|
|
4017
4045
|
const { "lint-staged": sourceLintStaged, ...sourceWithoutLintStaged } = source;
|
|
@@ -6307,7 +6335,7 @@ import { resolve as resolve8 } from "path";
|
|
|
6307
6335
|
|
|
6308
6336
|
// src/reconcile/managed-reconciler.ts
|
|
6309
6337
|
import { rm as rm6 } from "fs/promises";
|
|
6310
|
-
import { join as
|
|
6338
|
+
import { join as join26 } from "path";
|
|
6311
6339
|
|
|
6312
6340
|
// src/update/transaction-cas.ts
|
|
6313
6341
|
import { basename as basename6, dirname as dirname5 } from "path";
|
|
@@ -7372,7 +7400,8 @@ async function unresolvedPreservePaths(root, backupPath, state) {
|
|
|
7372
7400
|
const mutation = state.mutationEvidence?.find((evidence) => evidence.path === path2);
|
|
7373
7401
|
const matchesBackup = matchesExpected(actualHash, actualMode, backup, entry.mode);
|
|
7374
7402
|
const matchesOutcome = Boolean(mutation?.linearized && mutation.outcome && matchesExpected(actualHash, actualMode, mutation.outcome, mutation.outcomeMode));
|
|
7375
|
-
|
|
7403
|
+
const hasConflictArtifact = state.conflictArtifacts?.some((artifact) => artifact.path === path2) ?? false;
|
|
7404
|
+
if (!matchesBackup && (!matchesOutcome || hasConflictArtifact)) {
|
|
7376
7405
|
unresolved.push(path2);
|
|
7377
7406
|
}
|
|
7378
7407
|
}
|
|
@@ -7935,355 +7964,250 @@ async function applyTransaction(plan) {
|
|
|
7935
7964
|
|
|
7936
7965
|
// src/update/planner.ts
|
|
7937
7966
|
import { readFile as readFile17 } from "fs/promises";
|
|
7938
|
-
import { dirname as dirname6, join as
|
|
7939
|
-
|
|
7940
|
-
// src/update/docker-copy-update-plan.ts
|
|
7941
|
-
function deriveDockerfilePlans(files, snapshots, paths, targetWorkspaceManifests) {
|
|
7942
|
-
const rootPackage = virtualPathContent("package.json", files, snapshots, targetWorkspaceManifests);
|
|
7943
|
-
if (!rootPackage)
|
|
7944
|
-
return;
|
|
7945
|
-
const rootManifest = parsePackage(rootPackage, "package.json");
|
|
7946
|
-
const workspacePatterns = rootManifest.workspaces ?? [];
|
|
7947
|
-
const packages = [];
|
|
7948
|
-
for (const path2 of [
|
|
7949
|
-
...new Set([...paths, ...targetWorkspaceManifests.keys()])
|
|
7950
|
-
].filter((path3) => path3.endsWith("package.json"))) {
|
|
7951
|
-
const directory = path2 === "package.json" ? "." : path2.slice(0, -13);
|
|
7952
|
-
if (directory !== "." && !workspacePatterns.some((pattern) => matchesWorkspace(pattern, directory))) {
|
|
7953
|
-
continue;
|
|
7954
|
-
}
|
|
7955
|
-
const content = virtualPathContent(path2, files, snapshots, targetWorkspaceManifests);
|
|
7956
|
-
if (!content)
|
|
7957
|
-
continue;
|
|
7958
|
-
const manifest = parsePackage(content, path2);
|
|
7959
|
-
packages.push({
|
|
7960
|
-
name: manifest.name,
|
|
7961
|
-
directory,
|
|
7962
|
-
dependencies: [
|
|
7963
|
-
...Object.keys(manifest.dependencies ?? {}),
|
|
7964
|
-
...Object.keys(manifest.devDependencies ?? {})
|
|
7965
|
-
],
|
|
7966
|
-
workspaceDependencies: Object.entries({
|
|
7967
|
-
...manifest.dependencies,
|
|
7968
|
-
...manifest.devDependencies
|
|
7969
|
-
}).filter(([, range]) => range.startsWith("workspace:")).map(([name]) => name)
|
|
7970
|
-
});
|
|
7971
|
-
}
|
|
7972
|
-
for (const target of dockerfileCopyTargets) {
|
|
7973
|
-
const dockerPlan = files.find(({ path: path2 }) => path2 === target.dockerfilePath);
|
|
7974
|
-
if (!dockerPlan || dockerPlan.conflict)
|
|
7975
|
-
continue;
|
|
7976
|
-
const dockerSource = virtualContent(dockerPlan, snapshots);
|
|
7977
|
-
if (!dockerSource)
|
|
7978
|
-
continue;
|
|
7979
|
-
const source = new TextDecoder().decode(dockerSource);
|
|
7980
|
-
const next = syncDockerfileSource(source, target, packages);
|
|
7981
|
-
if (next === source)
|
|
7982
|
-
continue;
|
|
7983
|
-
dockerPlan.action = "update";
|
|
7984
|
-
dockerPlan.reason = "\u6309\u4E8B\u52A1\u5B89\u5168 workspace \u89C6\u56FE\u540C\u6B65 Docker COPY \u6E05\u5355";
|
|
7985
|
-
dockerPlan.content = new TextEncoder().encode(next);
|
|
7986
|
-
}
|
|
7987
|
-
}
|
|
7988
|
-
function virtualPathContent(path2, files, snapshots, targetWorkspaceManifests) {
|
|
7989
|
-
const plan = files.find((item) => item.path === path2);
|
|
7990
|
-
return plan ? virtualContent(plan, snapshots) : snapshots.get(path2) ?? targetWorkspaceManifests.get(path2);
|
|
7991
|
-
}
|
|
7992
|
-
function virtualContent(plan, snapshots) {
|
|
7993
|
-
if (plan.action === "delete")
|
|
7994
|
-
return;
|
|
7995
|
-
if (plan.action === "create" || plan.action === "update")
|
|
7996
|
-
return plan.content;
|
|
7997
|
-
return snapshots.get(plan.path);
|
|
7998
|
-
}
|
|
7999
|
-
function parsePackage(content, path2) {
|
|
8000
|
-
try {
|
|
8001
|
-
return JSON.parse(new TextDecoder().decode(content));
|
|
8002
|
-
} catch (error) {
|
|
8003
|
-
throw new Error(`\u65E0\u6CD5\u89E3\u6790 Docker workspace \u8F93\u5165\uFF1A${path2}`, {
|
|
8004
|
-
cause: error
|
|
8005
|
-
});
|
|
8006
|
-
}
|
|
8007
|
-
}
|
|
8008
|
-
function matchesWorkspace(pattern, directory) {
|
|
8009
|
-
return pattern.endsWith("/*") && directory.startsWith(pattern.slice(0, -1));
|
|
8010
|
-
}
|
|
7967
|
+
import { dirname as dirname6, join as join23 } from "path";
|
|
8011
7968
|
|
|
8012
|
-
// src/update/migration
|
|
8013
|
-
import {
|
|
8014
|
-
import { join as join17 } from "path";
|
|
7969
|
+
// src/update/docker-copy-legacy-migration.ts
|
|
7970
|
+
import { join as join19 } from "path";
|
|
8015
7971
|
|
|
8016
|
-
// src/update/
|
|
8017
|
-
import {
|
|
7972
|
+
// src/update/merge.ts
|
|
7973
|
+
import { mkdtemp, rm as rm5, writeFile as writeFile9 } from "fs/promises";
|
|
7974
|
+
import { tmpdir } from "os";
|
|
8018
7975
|
import { join as join16 } from "path";
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
7976
|
+
|
|
7977
|
+
// src/update/process.ts
|
|
7978
|
+
import { spawn as spawn2 } from "child_process";
|
|
7979
|
+
var LOCAL_GIT_ENVIRONMENT_VARIABLES = [
|
|
7980
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
7981
|
+
"GIT_CONFIG",
|
|
7982
|
+
"GIT_CONFIG_PARAMETERS",
|
|
7983
|
+
"GIT_CONFIG_COUNT",
|
|
7984
|
+
"GIT_OBJECT_DIRECTORY",
|
|
7985
|
+
"GIT_DIR",
|
|
7986
|
+
"GIT_WORK_TREE",
|
|
7987
|
+
"GIT_IMPLICIT_WORK_TREE",
|
|
7988
|
+
"GIT_GRAFT_FILE",
|
|
7989
|
+
"GIT_INDEX_FILE",
|
|
7990
|
+
"GIT_NO_REPLACE_OBJECTS",
|
|
7991
|
+
"GIT_REPLACE_REF_BASE",
|
|
7992
|
+
"GIT_PREFIX",
|
|
7993
|
+
"GIT_SHALLOW_FILE",
|
|
7994
|
+
"GIT_COMMON_DIR"
|
|
7995
|
+
];
|
|
7996
|
+
async function captureProcess(command, args, cwd, inherit = false) {
|
|
7997
|
+
return await new Promise((resolve6, reject) => {
|
|
7998
|
+
const child = spawn2(command, args, {
|
|
7999
|
+
cwd,
|
|
8000
|
+
env: withoutParentGitRepository(process.env),
|
|
8001
|
+
stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"]
|
|
8002
|
+
});
|
|
8003
|
+
let stdout2 = "";
|
|
8004
|
+
let stderr = "";
|
|
8005
|
+
child.stdout?.on("data", (chunk) => stdout2 += chunk.toString());
|
|
8006
|
+
child.stderr?.on("data", (chunk) => stderr += chunk.toString());
|
|
8007
|
+
child.once("error", reject);
|
|
8008
|
+
child.once("close", (code) => resolve6({ code: code ?? 1, stdout: stdout2, stderr }));
|
|
8009
|
+
});
|
|
8027
8010
|
}
|
|
8028
|
-
async function
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
try {
|
|
8034
|
-
content = await readFile12(join16(input.projectDirectory, path2));
|
|
8035
|
-
} catch (error) {
|
|
8036
|
-
if (!isMissingFileError(error))
|
|
8037
|
-
throw error;
|
|
8038
|
-
}
|
|
8039
|
-
if (input.projectSnapshots) {
|
|
8040
|
-
input.projectSnapshots.set(path2, content);
|
|
8011
|
+
async function assertProcess(command, args, cwd, inherit = false) {
|
|
8012
|
+
const result = await captureProcess(command, args, cwd, inherit);
|
|
8013
|
+
if (result.code !== 0) {
|
|
8014
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
8015
|
+
throw new Error(`${command} ${args.join(" ")} \u6267\u884C\u5931\u8D25${detail ? `\uFF1A${detail}` : ""}`);
|
|
8041
8016
|
}
|
|
8042
|
-
return
|
|
8017
|
+
return result;
|
|
8043
8018
|
}
|
|
8044
|
-
function
|
|
8045
|
-
|
|
8019
|
+
function withoutParentGitRepository(source) {
|
|
8020
|
+
const environment = { ...source };
|
|
8021
|
+
for (const key of LOCAL_GIT_ENVIRONMENT_VARIABLES) {
|
|
8022
|
+
delete environment[key];
|
|
8023
|
+
}
|
|
8024
|
+
return environment;
|
|
8046
8025
|
}
|
|
8047
8026
|
|
|
8048
|
-
// src/update/
|
|
8049
|
-
function
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8027
|
+
// src/update/merge.ts
|
|
8028
|
+
async function mergeTextFile(base, ours, theirs) {
|
|
8029
|
+
const root = await mkdtemp(join16(tmpdir(), "windy-merge-"));
|
|
8030
|
+
try {
|
|
8031
|
+
const oursPath = join16(root, "ours");
|
|
8032
|
+
const basePath = join16(root, "base");
|
|
8033
|
+
const theirsPath = join16(root, "theirs");
|
|
8034
|
+
await Promise.all([
|
|
8035
|
+
writeFile9(oursPath, ours),
|
|
8036
|
+
writeFile9(basePath, base),
|
|
8037
|
+
writeFile9(theirsPath, theirs)
|
|
8038
|
+
]);
|
|
8039
|
+
const result = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
|
|
8040
|
+
if (result.code === 0) {
|
|
8041
|
+
return { content: new TextEncoder().encode(result.stdout) };
|
|
8055
8042
|
}
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
const merged = structuredClone(current);
|
|
8060
|
-
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
8061
|
-
for (const key of keys) {
|
|
8062
|
-
if (path2.length === 0 && (key === "id" || key === "prevId"))
|
|
8063
|
-
continue;
|
|
8064
|
-
const nextPath = [...path2, key];
|
|
8065
|
-
if (!(key in after)) {
|
|
8066
|
-
if (!(key in current))
|
|
8067
|
-
continue;
|
|
8068
|
-
if (deepEqual(current[key], before[key]))
|
|
8069
|
-
delete merged[key];
|
|
8070
|
-
else
|
|
8071
|
-
conflicts.push(`${conflictPath}\uFF1A\u5E73\u53F0\u5220\u9664\u4E86\u4E1A\u52A1\u5DF2\u4FEE\u6539\u7684 snapshot \u8282\u70B9 ${nextPath.join(".")}`);
|
|
8072
|
-
continue;
|
|
8043
|
+
if (result.code === 1) {
|
|
8044
|
+
const resolved = resolveConservativeConflicts(result.stdout);
|
|
8045
|
+
return resolved ? { content: new TextEncoder().encode(resolved) } : { conflict: "\u4E09\u65B9\u5408\u5E76\u5B58\u5728\u91CD\u53E0\u4FEE\u6539" };
|
|
8073
8046
|
}
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8047
|
+
return { conflict: result.stderr.trim() || "git merge-file \u6267\u884C\u5931\u8D25" };
|
|
8048
|
+
} finally {
|
|
8049
|
+
await rm5(root, { recursive: true, force: true });
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
function resolveConservativeConflicts(content) {
|
|
8053
|
+
const lines = content.split(`
|
|
8054
|
+
`);
|
|
8055
|
+
const output = [];
|
|
8056
|
+
for (let index = 0;index < lines.length; index += 1) {
|
|
8057
|
+
if (!lines[index]?.startsWith("<<<<<<< ")) {
|
|
8058
|
+
output.push(lines[index] ?? "");
|
|
8080
8059
|
continue;
|
|
8081
8060
|
}
|
|
8082
|
-
|
|
8061
|
+
const baseMarker = lines.findIndex((line, candidate) => candidate > index && line.startsWith("||||||| "));
|
|
8062
|
+
const separator = lines.indexOf("=======", baseMarker + 1);
|
|
8063
|
+
const endMarker = lines.findIndex((line, candidate) => candidate > separator && line.startsWith(">>>>>>> "));
|
|
8064
|
+
if (baseMarker < 0 || separator < 0 || endMarker < 0)
|
|
8065
|
+
return;
|
|
8066
|
+
const ours = lines.slice(index + 1, baseMarker);
|
|
8067
|
+
const base = lines.slice(baseMarker + 1, separator);
|
|
8068
|
+
const theirs = lines.slice(separator + 1, endMarker);
|
|
8069
|
+
const resolved = resolveRegion(ours, base, theirs);
|
|
8070
|
+
if (!resolved)
|
|
8071
|
+
return;
|
|
8072
|
+
output.push(...resolved);
|
|
8073
|
+
index = endMarker;
|
|
8083
8074
|
}
|
|
8084
|
-
return
|
|
8085
|
-
|
|
8086
|
-
function isRecord(value) {
|
|
8087
|
-
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8075
|
+
return output.join(`
|
|
8076
|
+
`);
|
|
8088
8077
|
}
|
|
8089
|
-
function
|
|
8090
|
-
|
|
8078
|
+
function resolveRegion(ours, base, theirs) {
|
|
8079
|
+
const oursRebased = replaceSubsequence(ours, base, theirs);
|
|
8080
|
+
if (oursRebased)
|
|
8081
|
+
return oursRebased;
|
|
8082
|
+
const theirsRebased = replaceSubsequence(theirs, base, ours);
|
|
8083
|
+
if (theirsRebased)
|
|
8084
|
+
return theirsRebased;
|
|
8085
|
+
if (normalizeWhitespace(ours) === normalizeWhitespace(base))
|
|
8086
|
+
return theirs;
|
|
8087
|
+
if (normalizeWhitespace(theirs) === normalizeWhitespace(base))
|
|
8088
|
+
return ours;
|
|
8089
|
+
return applySemanticTokenEdits(ours, base, theirs) ?? mergeMarkdownTable(ours, base, theirs);
|
|
8091
8090
|
}
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
return unchanged(input.targetManifest);
|
|
8091
|
+
function replaceSubsequence(content, baseline, replacement) {
|
|
8092
|
+
if (!baseline.length || baseline.length > content.length)
|
|
8093
|
+
return;
|
|
8094
|
+
const starts = [];
|
|
8095
|
+
for (let index = 0;index <= content.length - baseline.length; index += 1) {
|
|
8096
|
+
if (baseline.every((line, offset) => content[index + offset] === line)) {
|
|
8097
|
+
starts.push(index);
|
|
8098
|
+
}
|
|
8101
8099
|
}
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
const baseNames = new Set(base.entries.map(({ tag }) => stableName(tag)));
|
|
8111
|
-
const projectNames = new Set(project.entries.map(({ tag }) => stableName(tag)));
|
|
8112
|
-
const managedNames = new Set(input.sourceManifest.files.filter(({ path: path2 }) => isSqlMigration(path2)).map(({ path: path2 }) => stableName(path2.slice(drizzleRoot.length + 1, -4))));
|
|
8113
|
-
const candidates = target.entries.filter(({ tag }) => !baseNames.has(stableName(tag)));
|
|
8114
|
-
for (const { tag } of candidates) {
|
|
8115
|
-
const name = stableName(tag);
|
|
8116
|
-
if (projectNames.has(name) && !managedNames.has(name)) {
|
|
8117
|
-
conflicts.push(`${journalPath}\uFF1A\u5E73\u53F0 migration \u540D\u79F0\u4E0E\u4E0B\u6E38\u4E1A\u52A1\u5386\u53F2\u51B2\u7A81 ${name}`);
|
|
8118
|
-
}
|
|
8119
|
-
}
|
|
8120
|
-
const additions = candidates.filter(({ tag }) => !projectNames.has(stableName(tag)));
|
|
8121
|
-
const contentOverrides = new Map;
|
|
8122
|
-
const files = await preservePublishedHistory(input.sourceManifest, input, contentOverrides);
|
|
8123
|
-
const publishedPaths = new Set(files.map(({ path: path2 }) => path2));
|
|
8124
|
-
files.push(...input.targetManifest.files.filter(({ path: path2 }) => !isDrizzleHistoryPath(path2)));
|
|
8125
|
-
if (!additions.length) {
|
|
8126
|
-
const journalContent2 = encodeJson(project);
|
|
8127
|
-
contentOverrides.set(journalPath, journalContent2);
|
|
8128
|
-
files.push(managedEntry(journalPath, journalContent2));
|
|
8129
|
-
return result(input.targetManifest, files, contentOverrides, publishedPaths, conflicts);
|
|
8130
|
-
}
|
|
8131
|
-
let nextPrefix = highestPrefix(project) + 1;
|
|
8132
|
-
let nextIndex = highestIndex(project) + 1;
|
|
8133
|
-
let previousWhen = project.entries.at(-1)?.when ?? 0;
|
|
8134
|
-
let currentSnapshot = await readLatestSnapshot(input, project);
|
|
8135
|
-
const mergedJournal = structuredClone(project);
|
|
8136
|
-
for (const addition of additions) {
|
|
8137
|
-
const targetPosition = target.entries.indexOf(addition);
|
|
8138
|
-
const previousTarget = target.entries[targetPosition - 1];
|
|
8139
|
-
if (!previousTarget) {
|
|
8140
|
-
conflicts.push(`${journalPath}\uFF1A\u76EE\u6807 migration \u7F3A\u5C11\u524D\u7F6E\u5FEB\u7167`);
|
|
8141
|
-
break;
|
|
8142
|
-
}
|
|
8143
|
-
const targetBefore = await readSnapshot(input.targetDirectory, previousTarget.tag);
|
|
8144
|
-
const targetAfter = await readSnapshot(input.targetDirectory, addition.tag);
|
|
8145
|
-
const merged = applySnapshotDelta(targetBefore, targetAfter, currentSnapshot, [], conflicts, journalPath);
|
|
8146
|
-
if (conflicts.length)
|
|
8147
|
-
break;
|
|
8148
|
-
const prefix = String(nextPrefix).padStart(4, "0");
|
|
8149
|
-
const name = stableName(addition.tag);
|
|
8150
|
-
const tag = `${prefix}_${name}`;
|
|
8151
|
-
const sqlPath = `${drizzleRoot}/${tag}.sql`;
|
|
8152
|
-
const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
|
|
8153
|
-
const sql = await readFile13(join17(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
|
|
8154
|
-
merged.id = targetAfter.id;
|
|
8155
|
-
merged.prevId = currentSnapshot.id;
|
|
8156
|
-
const snapshot = encodeJson(merged);
|
|
8157
|
-
contentOverrides.set(sqlPath, sql);
|
|
8158
|
-
contentOverrides.set(snapshotPath, snapshot);
|
|
8159
|
-
files.push(managedEntry(sqlPath, sql), managedEntry(snapshotPath, snapshot));
|
|
8160
|
-
previousWhen = Math.max(previousWhen + 1, addition.when);
|
|
8161
|
-
mergedJournal.entries.push({
|
|
8162
|
-
...addition,
|
|
8163
|
-
idx: nextIndex,
|
|
8164
|
-
when: previousWhen,
|
|
8165
|
-
tag
|
|
8166
|
-
});
|
|
8167
|
-
currentSnapshot = merged;
|
|
8168
|
-
nextPrefix += 1;
|
|
8169
|
-
nextIndex += 1;
|
|
8170
|
-
}
|
|
8171
|
-
const journalContent = encodeJson(mergedJournal);
|
|
8172
|
-
contentOverrides.set(journalPath, journalContent);
|
|
8173
|
-
files.push(managedEntry(journalPath, journalContent));
|
|
8174
|
-
return result(input.targetManifest, files, contentOverrides, publishedPaths, conflicts);
|
|
8175
|
-
}
|
|
8176
|
-
function result(targetManifest, files, contentOverrides, forcePreserves, conflicts) {
|
|
8177
|
-
return {
|
|
8178
|
-
targetManifest: {
|
|
8179
|
-
...targetManifest,
|
|
8180
|
-
files: deduplicate(files).sort((left, right) => left.path.localeCompare(right.path))
|
|
8181
|
-
},
|
|
8182
|
-
contentOverrides,
|
|
8183
|
-
forceUpdates: new Set([journalPath]),
|
|
8184
|
-
forcePreserves,
|
|
8185
|
-
conflicts
|
|
8186
|
-
};
|
|
8100
|
+
if (starts.length !== 1)
|
|
8101
|
+
return;
|
|
8102
|
+
const start = starts[0] ?? 0;
|
|
8103
|
+
return [
|
|
8104
|
+
...content.slice(0, start),
|
|
8105
|
+
...replacement,
|
|
8106
|
+
...content.slice(start + baseline.length)
|
|
8107
|
+
];
|
|
8187
8108
|
}
|
|
8188
|
-
function
|
|
8189
|
-
return
|
|
8190
|
-
targetManifest,
|
|
8191
|
-
contentOverrides: new Map,
|
|
8192
|
-
forceUpdates: new Set,
|
|
8193
|
-
forcePreserves: new Set,
|
|
8194
|
-
conflicts: []
|
|
8195
|
-
};
|
|
8109
|
+
function normalizeWhitespace(lines) {
|
|
8110
|
+
return lines.join(" ").replaceAll(/\s+/g, " ").trim();
|
|
8196
8111
|
}
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8112
|
+
function applySemanticTokenEdits(oursLines, baseLines, theirsLines) {
|
|
8113
|
+
const oursText = oursLines.join(`
|
|
8114
|
+
`);
|
|
8115
|
+
const baseText = baseLines.join(`
|
|
8116
|
+
`);
|
|
8117
|
+
const theirsText = theirsLines.join(`
|
|
8118
|
+
`);
|
|
8119
|
+
const ours = semanticTokens(oursText);
|
|
8120
|
+
const base = semanticTokens(baseText);
|
|
8121
|
+
const theirs = semanticTokens(theirsText);
|
|
8122
|
+
let prefix = 0;
|
|
8123
|
+
while (prefix < base.length && prefix < theirs.length && base[prefix]?.value === theirs[prefix]?.value) {
|
|
8124
|
+
prefix += 1;
|
|
8209
8125
|
}
|
|
8126
|
+
let suffix = 0;
|
|
8127
|
+
while (suffix < base.length - prefix && suffix < theirs.length - prefix && base[base.length - 1 - suffix]?.value === theirs[theirs.length - 1 - suffix]?.value) {
|
|
8128
|
+
suffix += 1;
|
|
8129
|
+
}
|
|
8130
|
+
const baseEnd = base.length - suffix;
|
|
8131
|
+
const targetEnd = theirs.length - suffix;
|
|
8132
|
+
const needle = base.slice(prefix, baseEnd).map(({ value }) => value);
|
|
8133
|
+
if (!needle.length)
|
|
8134
|
+
return;
|
|
8135
|
+
const starts = findTokenSequence(ours, needle);
|
|
8136
|
+
if (starts.length !== 1)
|
|
8137
|
+
return;
|
|
8138
|
+
const oursStart = starts[0] ?? 0;
|
|
8139
|
+
const oursEnd = oursStart + needle.length - 1;
|
|
8140
|
+
const targetFirst = theirs[prefix];
|
|
8141
|
+
const targetLast = theirs[targetEnd - 1];
|
|
8142
|
+
const replacement = targetFirst && targetLast ? theirsText.slice(targetFirst.start, targetLast.end) : "";
|
|
8143
|
+
const merged = oursText.slice(0, ours[oursStart]?.start ?? 0) + replacement + oursText.slice(ours[oursEnd]?.end ?? 0);
|
|
8144
|
+
return merged.split(`
|
|
8145
|
+
`);
|
|
8210
8146
|
}
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
throw new Error(`\u5F53\u524D\u9879\u76EE migration \u6587\u4EF6\u7F3A\u5931\uFF1A${path2}`);
|
|
8217
|
-
overrides.set(path2, content);
|
|
8147
|
+
function semanticTokens(content) {
|
|
8148
|
+
return [...content.matchAll(/[A-Za-z0-9_@./:-]+|[^\s]/g)].map((match) => ({
|
|
8149
|
+
value: match[0],
|
|
8150
|
+
start: match.index,
|
|
8151
|
+
end: match.index + match[0].length
|
|
8218
8152
|
}));
|
|
8219
|
-
return entries;
|
|
8220
8153
|
}
|
|
8221
|
-
|
|
8222
|
-
const
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
throw new Error(`${journalPath}\uFF1Amigration tag \u683C\u5F0F\u65E0\u6548\uFF1A${latest.tag}`);
|
|
8228
|
-
}
|
|
8229
|
-
const snapshotPath = `${drizzleRoot}/meta/${match[1]}_snapshot.json`;
|
|
8230
|
-
const content = await readProjectFile(input, snapshotPath);
|
|
8231
|
-
if (!content) {
|
|
8232
|
-
throw new Error(`\u5F53\u524D\u9879\u76EE migration \u6587\u4EF6\u7F3A\u5931\uFF1A${snapshotPath}`);
|
|
8154
|
+
function findTokenSequence(tokens, needle) {
|
|
8155
|
+
const starts = [];
|
|
8156
|
+
for (let index = 0;index <= tokens.length - needle.length; index += 1) {
|
|
8157
|
+
if (needle.every((value, offset) => tokens[index + offset]?.value === value)) {
|
|
8158
|
+
starts.push(index);
|
|
8159
|
+
}
|
|
8233
8160
|
}
|
|
8234
|
-
return
|
|
8235
|
-
}
|
|
8236
|
-
async function readSnapshot(root, tag) {
|
|
8237
|
-
const match = migrationPattern.exec(tag);
|
|
8238
|
-
if (!match)
|
|
8239
|
-
throw new Error(`${journalPath}\uFF1Amigration tag \u683C\u5F0F\u65E0\u6548\uFF1A${tag}`);
|
|
8240
|
-
return await readJson(join17(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
|
|
8161
|
+
return starts;
|
|
8241
8162
|
}
|
|
8242
|
-
function
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8163
|
+
function mergeMarkdownTable(ours, base, theirs) {
|
|
8164
|
+
if (![ours, base, theirs].every((rows) => rows.length === base.length && rows.every(isMarkdownTableRow))) {
|
|
8165
|
+
return;
|
|
8166
|
+
}
|
|
8167
|
+
const parsed = [ours, base, theirs].map((rows) => rows.map(parseTableRow));
|
|
8168
|
+
const [oursRows, baseRows, theirsRows] = parsed;
|
|
8169
|
+
if (!oursRows || !baseRows || !theirsRows || !oursRows.every((cells, index) => cells.length === baseRows[index]?.length && cells.length === theirsRows[index]?.length)) {
|
|
8170
|
+
return;
|
|
8171
|
+
}
|
|
8172
|
+
const merged = [];
|
|
8173
|
+
for (let row = 0;row < baseRows.length; row += 1) {
|
|
8174
|
+
const cells = [];
|
|
8175
|
+
for (let column = 0;column < (baseRows[row]?.length ?? 0); column += 1) {
|
|
8176
|
+
const baseCell = baseRows[row]?.[column] ?? "";
|
|
8177
|
+
const oursCell = oursRows[row]?.[column] ?? "";
|
|
8178
|
+
const theirsCell = theirsRows[row]?.[column] ?? "";
|
|
8179
|
+
if (isTableSeparator(baseCell)) {
|
|
8180
|
+
cells.push("---");
|
|
8181
|
+
} else if (oursCell === baseCell || oursCell === theirsCell) {
|
|
8182
|
+
cells.push(theirsCell);
|
|
8183
|
+
} else if (theirsCell === baseCell) {
|
|
8184
|
+
cells.push(oursCell);
|
|
8185
|
+
} else {
|
|
8186
|
+
return;
|
|
8187
|
+
}
|
|
8248
8188
|
}
|
|
8249
|
-
|
|
8189
|
+
merged.push(`| ${cells.join(" | ")} |`);
|
|
8250
8190
|
}
|
|
8191
|
+
return merged;
|
|
8251
8192
|
}
|
|
8252
|
-
function
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
function highestPrefix(journal) {
|
|
8256
|
-
return Math.max(-1, ...journal.entries.map(({ tag }) => Number(migrationPattern.exec(tag)?.[1] ?? -1)));
|
|
8257
|
-
}
|
|
8258
|
-
function highestIndex(journal) {
|
|
8259
|
-
return Math.max(-1, ...journal.entries.map(({ idx }) => idx));
|
|
8260
|
-
}
|
|
8261
|
-
function isDrizzleHistoryPath(path2) {
|
|
8262
|
-
return path2 === journalPath || isMigrationArtifact(path2);
|
|
8263
|
-
}
|
|
8264
|
-
function isMigrationArtifact(path2) {
|
|
8265
|
-
return isSqlMigration(path2) || new RegExp(`^${drizzleRoot}/meta/\\d{4}_snapshot\\.json$`).test(path2);
|
|
8266
|
-
}
|
|
8267
|
-
function isSqlMigration(path2) {
|
|
8268
|
-
return new RegExp(`^${drizzleRoot}/\\d{4}_.+\\.sql$`).test(path2);
|
|
8269
|
-
}
|
|
8270
|
-
function managedEntry(path2, content) {
|
|
8271
|
-
return { path: path2, ownership: "windy-managed", hash: hashContent(content) };
|
|
8272
|
-
}
|
|
8273
|
-
function deduplicate(files) {
|
|
8274
|
-
return [...new Map(files.map((entry) => [entry.path, entry])).values()];
|
|
8193
|
+
function isMarkdownTableRow(line) {
|
|
8194
|
+
const trimmed = line.trim();
|
|
8195
|
+
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
8275
8196
|
}
|
|
8276
|
-
function
|
|
8277
|
-
return
|
|
8278
|
-
`);
|
|
8197
|
+
function parseTableRow(line) {
|
|
8198
|
+
return line.trim().slice(1, -1).split("|").map((cell) => cell.trim());
|
|
8279
8199
|
}
|
|
8280
|
-
|
|
8281
|
-
return
|
|
8200
|
+
function isTableSeparator(cell) {
|
|
8201
|
+
return /^:?-{3,}:?$/.test(cell);
|
|
8282
8202
|
}
|
|
8283
8203
|
|
|
8204
|
+
// src/update/planner-snapshots.ts
|
|
8205
|
+
import { readFile as readFile13, stat as stat2 } from "fs/promises";
|
|
8206
|
+
import { join as join18 } from "path";
|
|
8207
|
+
|
|
8284
8208
|
// src/update/planner-file.ts
|
|
8285
|
-
import { readFile as
|
|
8286
|
-
import { join as
|
|
8209
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
8210
|
+
import { join as join17 } from "path";
|
|
8287
8211
|
|
|
8288
8212
|
// src/update/known-repairs.ts
|
|
8289
8213
|
import { createHash as createHash5 } from "crypto";
|
|
@@ -8487,238 +8411,6 @@ function normalizeTableCell(cell) {
|
|
|
8487
8411
|
return separator ? `${separator[1]}---${separator[2]}` : cell;
|
|
8488
8412
|
}
|
|
8489
8413
|
|
|
8490
|
-
// src/update/merge.ts
|
|
8491
|
-
import { mkdtemp, rm as rm5, writeFile as writeFile9 } from "fs/promises";
|
|
8492
|
-
import { tmpdir } from "os";
|
|
8493
|
-
import { join as join18 } from "path";
|
|
8494
|
-
|
|
8495
|
-
// src/update/process.ts
|
|
8496
|
-
import { spawn as spawn2 } from "child_process";
|
|
8497
|
-
var LOCAL_GIT_ENVIRONMENT_VARIABLES = [
|
|
8498
|
-
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
8499
|
-
"GIT_CONFIG",
|
|
8500
|
-
"GIT_CONFIG_PARAMETERS",
|
|
8501
|
-
"GIT_CONFIG_COUNT",
|
|
8502
|
-
"GIT_OBJECT_DIRECTORY",
|
|
8503
|
-
"GIT_DIR",
|
|
8504
|
-
"GIT_WORK_TREE",
|
|
8505
|
-
"GIT_IMPLICIT_WORK_TREE",
|
|
8506
|
-
"GIT_GRAFT_FILE",
|
|
8507
|
-
"GIT_INDEX_FILE",
|
|
8508
|
-
"GIT_NO_REPLACE_OBJECTS",
|
|
8509
|
-
"GIT_REPLACE_REF_BASE",
|
|
8510
|
-
"GIT_PREFIX",
|
|
8511
|
-
"GIT_SHALLOW_FILE",
|
|
8512
|
-
"GIT_COMMON_DIR"
|
|
8513
|
-
];
|
|
8514
|
-
async function captureProcess(command, args, cwd, inherit = false) {
|
|
8515
|
-
return await new Promise((resolve6, reject) => {
|
|
8516
|
-
const child = spawn2(command, args, {
|
|
8517
|
-
cwd,
|
|
8518
|
-
env: withoutParentGitRepository(process.env),
|
|
8519
|
-
stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"]
|
|
8520
|
-
});
|
|
8521
|
-
let stdout2 = "";
|
|
8522
|
-
let stderr = "";
|
|
8523
|
-
child.stdout?.on("data", (chunk) => stdout2 += chunk.toString());
|
|
8524
|
-
child.stderr?.on("data", (chunk) => stderr += chunk.toString());
|
|
8525
|
-
child.once("error", reject);
|
|
8526
|
-
child.once("close", (code) => resolve6({ code: code ?? 1, stdout: stdout2, stderr }));
|
|
8527
|
-
});
|
|
8528
|
-
}
|
|
8529
|
-
async function assertProcess(command, args, cwd, inherit = false) {
|
|
8530
|
-
const result2 = await captureProcess(command, args, cwd, inherit);
|
|
8531
|
-
if (result2.code !== 0) {
|
|
8532
|
-
const detail = result2.stderr.trim() || result2.stdout.trim();
|
|
8533
|
-
throw new Error(`${command} ${args.join(" ")} \u6267\u884C\u5931\u8D25${detail ? `\uFF1A${detail}` : ""}`);
|
|
8534
|
-
}
|
|
8535
|
-
return result2;
|
|
8536
|
-
}
|
|
8537
|
-
function withoutParentGitRepository(source) {
|
|
8538
|
-
const environment = { ...source };
|
|
8539
|
-
for (const key of LOCAL_GIT_ENVIRONMENT_VARIABLES) {
|
|
8540
|
-
delete environment[key];
|
|
8541
|
-
}
|
|
8542
|
-
return environment;
|
|
8543
|
-
}
|
|
8544
|
-
|
|
8545
|
-
// src/update/merge.ts
|
|
8546
|
-
async function mergeTextFile(base, ours, theirs) {
|
|
8547
|
-
const root = await mkdtemp(join18(tmpdir(), "windy-merge-"));
|
|
8548
|
-
try {
|
|
8549
|
-
const oursPath = join18(root, "ours");
|
|
8550
|
-
const basePath = join18(root, "base");
|
|
8551
|
-
const theirsPath = join18(root, "theirs");
|
|
8552
|
-
await Promise.all([
|
|
8553
|
-
writeFile9(oursPath, ours),
|
|
8554
|
-
writeFile9(basePath, base),
|
|
8555
|
-
writeFile9(theirsPath, theirs)
|
|
8556
|
-
]);
|
|
8557
|
-
const result2 = await captureProcess("git", ["merge-file", "-p", "--diff3", oursPath, basePath, theirsPath], root);
|
|
8558
|
-
if (result2.code === 0) {
|
|
8559
|
-
return { content: new TextEncoder().encode(result2.stdout) };
|
|
8560
|
-
}
|
|
8561
|
-
if (result2.code === 1) {
|
|
8562
|
-
const resolved = resolveConservativeConflicts(result2.stdout);
|
|
8563
|
-
return resolved ? { content: new TextEncoder().encode(resolved) } : { conflict: "\u4E09\u65B9\u5408\u5E76\u5B58\u5728\u91CD\u53E0\u4FEE\u6539" };
|
|
8564
|
-
}
|
|
8565
|
-
return { conflict: result2.stderr.trim() || "git merge-file \u6267\u884C\u5931\u8D25" };
|
|
8566
|
-
} finally {
|
|
8567
|
-
await rm5(root, { recursive: true, force: true });
|
|
8568
|
-
}
|
|
8569
|
-
}
|
|
8570
|
-
function resolveConservativeConflicts(content) {
|
|
8571
|
-
const lines = content.split(`
|
|
8572
|
-
`);
|
|
8573
|
-
const output = [];
|
|
8574
|
-
for (let index = 0;index < lines.length; index += 1) {
|
|
8575
|
-
if (!lines[index]?.startsWith("<<<<<<< ")) {
|
|
8576
|
-
output.push(lines[index] ?? "");
|
|
8577
|
-
continue;
|
|
8578
|
-
}
|
|
8579
|
-
const baseMarker = lines.findIndex((line, candidate) => candidate > index && line.startsWith("||||||| "));
|
|
8580
|
-
const separator = lines.indexOf("=======", baseMarker + 1);
|
|
8581
|
-
const endMarker = lines.findIndex((line, candidate) => candidate > separator && line.startsWith(">>>>>>> "));
|
|
8582
|
-
if (baseMarker < 0 || separator < 0 || endMarker < 0)
|
|
8583
|
-
return;
|
|
8584
|
-
const ours = lines.slice(index + 1, baseMarker);
|
|
8585
|
-
const base = lines.slice(baseMarker + 1, separator);
|
|
8586
|
-
const theirs = lines.slice(separator + 1, endMarker);
|
|
8587
|
-
const resolved = resolveRegion(ours, base, theirs);
|
|
8588
|
-
if (!resolved)
|
|
8589
|
-
return;
|
|
8590
|
-
output.push(...resolved);
|
|
8591
|
-
index = endMarker;
|
|
8592
|
-
}
|
|
8593
|
-
return output.join(`
|
|
8594
|
-
`);
|
|
8595
|
-
}
|
|
8596
|
-
function resolveRegion(ours, base, theirs) {
|
|
8597
|
-
const oursRebased = replaceSubsequence(ours, base, theirs);
|
|
8598
|
-
if (oursRebased)
|
|
8599
|
-
return oursRebased;
|
|
8600
|
-
const theirsRebased = replaceSubsequence(theirs, base, ours);
|
|
8601
|
-
if (theirsRebased)
|
|
8602
|
-
return theirsRebased;
|
|
8603
|
-
if (normalizeWhitespace(ours) === normalizeWhitespace(base))
|
|
8604
|
-
return theirs;
|
|
8605
|
-
if (normalizeWhitespace(theirs) === normalizeWhitespace(base))
|
|
8606
|
-
return ours;
|
|
8607
|
-
return applySemanticTokenEdits(ours, base, theirs) ?? mergeMarkdownTable(ours, base, theirs);
|
|
8608
|
-
}
|
|
8609
|
-
function replaceSubsequence(content, baseline, replacement) {
|
|
8610
|
-
if (!baseline.length || baseline.length > content.length)
|
|
8611
|
-
return;
|
|
8612
|
-
const starts = [];
|
|
8613
|
-
for (let index = 0;index <= content.length - baseline.length; index += 1) {
|
|
8614
|
-
if (baseline.every((line, offset) => content[index + offset] === line)) {
|
|
8615
|
-
starts.push(index);
|
|
8616
|
-
}
|
|
8617
|
-
}
|
|
8618
|
-
if (starts.length !== 1)
|
|
8619
|
-
return;
|
|
8620
|
-
const start = starts[0] ?? 0;
|
|
8621
|
-
return [
|
|
8622
|
-
...content.slice(0, start),
|
|
8623
|
-
...replacement,
|
|
8624
|
-
...content.slice(start + baseline.length)
|
|
8625
|
-
];
|
|
8626
|
-
}
|
|
8627
|
-
function normalizeWhitespace(lines) {
|
|
8628
|
-
return lines.join(" ").replaceAll(/\s+/g, " ").trim();
|
|
8629
|
-
}
|
|
8630
|
-
function applySemanticTokenEdits(oursLines, baseLines, theirsLines) {
|
|
8631
|
-
const oursText = oursLines.join(`
|
|
8632
|
-
`);
|
|
8633
|
-
const baseText = baseLines.join(`
|
|
8634
|
-
`);
|
|
8635
|
-
const theirsText = theirsLines.join(`
|
|
8636
|
-
`);
|
|
8637
|
-
const ours = semanticTokens(oursText);
|
|
8638
|
-
const base = semanticTokens(baseText);
|
|
8639
|
-
const theirs = semanticTokens(theirsText);
|
|
8640
|
-
let prefix = 0;
|
|
8641
|
-
while (prefix < base.length && prefix < theirs.length && base[prefix]?.value === theirs[prefix]?.value) {
|
|
8642
|
-
prefix += 1;
|
|
8643
|
-
}
|
|
8644
|
-
let suffix = 0;
|
|
8645
|
-
while (suffix < base.length - prefix && suffix < theirs.length - prefix && base[base.length - 1 - suffix]?.value === theirs[theirs.length - 1 - suffix]?.value) {
|
|
8646
|
-
suffix += 1;
|
|
8647
|
-
}
|
|
8648
|
-
const baseEnd = base.length - suffix;
|
|
8649
|
-
const targetEnd = theirs.length - suffix;
|
|
8650
|
-
const needle = base.slice(prefix, baseEnd).map(({ value }) => value);
|
|
8651
|
-
if (!needle.length)
|
|
8652
|
-
return;
|
|
8653
|
-
const starts = findTokenSequence(ours, needle);
|
|
8654
|
-
if (starts.length !== 1)
|
|
8655
|
-
return;
|
|
8656
|
-
const oursStart = starts[0] ?? 0;
|
|
8657
|
-
const oursEnd = oursStart + needle.length - 1;
|
|
8658
|
-
const targetFirst = theirs[prefix];
|
|
8659
|
-
const targetLast = theirs[targetEnd - 1];
|
|
8660
|
-
const replacement = targetFirst && targetLast ? theirsText.slice(targetFirst.start, targetLast.end) : "";
|
|
8661
|
-
const merged = oursText.slice(0, ours[oursStart]?.start ?? 0) + replacement + oursText.slice(ours[oursEnd]?.end ?? 0);
|
|
8662
|
-
return merged.split(`
|
|
8663
|
-
`);
|
|
8664
|
-
}
|
|
8665
|
-
function semanticTokens(content) {
|
|
8666
|
-
return [...content.matchAll(/[A-Za-z0-9_@./:-]+|[^\s]/g)].map((match) => ({
|
|
8667
|
-
value: match[0],
|
|
8668
|
-
start: match.index,
|
|
8669
|
-
end: match.index + match[0].length
|
|
8670
|
-
}));
|
|
8671
|
-
}
|
|
8672
|
-
function findTokenSequence(tokens, needle) {
|
|
8673
|
-
const starts = [];
|
|
8674
|
-
for (let index = 0;index <= tokens.length - needle.length; index += 1) {
|
|
8675
|
-
if (needle.every((value, offset) => tokens[index + offset]?.value === value)) {
|
|
8676
|
-
starts.push(index);
|
|
8677
|
-
}
|
|
8678
|
-
}
|
|
8679
|
-
return starts;
|
|
8680
|
-
}
|
|
8681
|
-
function mergeMarkdownTable(ours, base, theirs) {
|
|
8682
|
-
if (![ours, base, theirs].every((rows) => rows.length === base.length && rows.every(isMarkdownTableRow))) {
|
|
8683
|
-
return;
|
|
8684
|
-
}
|
|
8685
|
-
const parsed = [ours, base, theirs].map((rows) => rows.map(parseTableRow));
|
|
8686
|
-
const [oursRows, baseRows, theirsRows] = parsed;
|
|
8687
|
-
if (!oursRows || !baseRows || !theirsRows || !oursRows.every((cells, index) => cells.length === baseRows[index]?.length && cells.length === theirsRows[index]?.length)) {
|
|
8688
|
-
return;
|
|
8689
|
-
}
|
|
8690
|
-
const merged = [];
|
|
8691
|
-
for (let row = 0;row < baseRows.length; row += 1) {
|
|
8692
|
-
const cells = [];
|
|
8693
|
-
for (let column = 0;column < (baseRows[row]?.length ?? 0); column += 1) {
|
|
8694
|
-
const baseCell = baseRows[row]?.[column] ?? "";
|
|
8695
|
-
const oursCell = oursRows[row]?.[column] ?? "";
|
|
8696
|
-
const theirsCell = theirsRows[row]?.[column] ?? "";
|
|
8697
|
-
if (isTableSeparator(baseCell)) {
|
|
8698
|
-
cells.push("---");
|
|
8699
|
-
} else if (oursCell === baseCell || oursCell === theirsCell) {
|
|
8700
|
-
cells.push(theirsCell);
|
|
8701
|
-
} else if (theirsCell === baseCell) {
|
|
8702
|
-
cells.push(oursCell);
|
|
8703
|
-
} else {
|
|
8704
|
-
return;
|
|
8705
|
-
}
|
|
8706
|
-
}
|
|
8707
|
-
merged.push(`| ${cells.join(" | ")} |`);
|
|
8708
|
-
}
|
|
8709
|
-
return merged;
|
|
8710
|
-
}
|
|
8711
|
-
function isMarkdownTableRow(line) {
|
|
8712
|
-
const trimmed = line.trim();
|
|
8713
|
-
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
8714
|
-
}
|
|
8715
|
-
function parseTableRow(line) {
|
|
8716
|
-
return line.trim().slice(1, -1).split("|").map((cell) => cell.trim());
|
|
8717
|
-
}
|
|
8718
|
-
function isTableSeparator(cell) {
|
|
8719
|
-
return /^:?-{3,}:?$/.test(cell);
|
|
8720
|
-
}
|
|
8721
|
-
|
|
8722
8414
|
// src/update/package-merge.ts
|
|
8723
8415
|
function mergePackageJson(base, ours, theirs) {
|
|
8724
8416
|
const conflicts = [];
|
|
@@ -8806,8 +8498,8 @@ function conflict(path2, reason, expectedProjectContent) {
|
|
|
8806
8498
|
// src/update/planner-file.ts
|
|
8807
8499
|
async function planFile(path2, baseRoot, targetRoot, source, target, ours, provenance, targetContentOverride, manageGeneratedProjectOwned = false, forcePlatformBaseline = false) {
|
|
8808
8500
|
const [base, theirs] = await Promise.all([
|
|
8809
|
-
readOptional(
|
|
8810
|
-
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(
|
|
8501
|
+
readOptional(join17(baseRoot, path2)),
|
|
8502
|
+
targetContentOverride ? Promise.resolve(targetContentOverride) : readOptional(join17(targetRoot, path2))
|
|
8811
8503
|
]);
|
|
8812
8504
|
const ownership = source?.ownership === "project-owned" ? source.ownership : target?.ownership || source?.ownership || classifyOwnership(path2);
|
|
8813
8505
|
const expected = expectationForSnapshot(ours);
|
|
@@ -8853,7 +8545,8 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
8853
8545
|
const oursChanged = hashContent(ours) !== source.hash;
|
|
8854
8546
|
const sourceArtifactHash = artifactHashOf(source);
|
|
8855
8547
|
const targetChanged = targetChangedSinceBase(sourceArtifactHash, base, theirs);
|
|
8856
|
-
const
|
|
8548
|
+
const adoptedProjectDelta = source.artifactHash !== undefined && source.artifactHash !== source.hash && source.reconciliation === undefined;
|
|
8549
|
+
const forceTargetBaseline = !adoptedProjectDelta && shouldForcePlatformBaseline({
|
|
8857
8550
|
forcePlatformBaseline,
|
|
8858
8551
|
oursChanged,
|
|
8859
8552
|
sourceArtifactHash,
|
|
@@ -8873,123 +8566,630 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
8873
8566
|
const merged = mergePackageJson(base, ours, theirs);
|
|
8874
8567
|
return merged.content ? readyPlannedFile(path2, ownership, "update", "package.json \u5B57\u6BB5\u7EA7\u5408\u5E76", expected, merged.content) : conflict2(path2, ownership, `\u5B57\u6BB5\u51B2\u7A81\uFF1A${merged.conflicts.join("\u3001")}`, expected);
|
|
8875
8568
|
}
|
|
8876
|
-
if (!oursChanged) {
|
|
8877
|
-
|
|
8569
|
+
if (!oursChanged && targetChanged && adoptedProjectDelta && base) {
|
|
8570
|
+
const merged = await mergeTextFile(base, ours, theirs);
|
|
8571
|
+
return merged.content ? readyPlannedFile(path2, ownership, "update", "\u4E09\u65B9\u5408\u5E76\u5DF2\u91C7\u7EB3\u9879\u76EE\u589E\u91CF", expected, merged.content) : conflict2(path2, ownership, merged.conflict || "\u4E09\u65B9\u5408\u5E76\u5931\u8D25", expected);
|
|
8572
|
+
}
|
|
8573
|
+
if (!oursChanged) {
|
|
8574
|
+
return readyPlannedFile(path2, ownership, "update", "\u4EC5 Windy \u4FEE\u6539", expected, theirs);
|
|
8575
|
+
}
|
|
8576
|
+
if (!targetChanged) {
|
|
8577
|
+
return readyPlannedFile(path2, ownership, "preserve", "\u5BA2\u6237\u5DF2\u4FEE\u6539", expected);
|
|
8578
|
+
}
|
|
8579
|
+
if (hashContent(ours) === target.hash) {
|
|
8580
|
+
return readyPlannedFile(path2, ownership, "preserve", "\u5DF2\u5305\u542B\u76EE\u6807\u5185\u5BB9", expected);
|
|
8581
|
+
}
|
|
8582
|
+
if (base) {
|
|
8583
|
+
const merged = await mergeTextFile(base, ours, theirs);
|
|
8584
|
+
return merged.content ? readyPlannedFile(path2, ownership, "update", "\u4E09\u65B9\u5408\u5E76", expected, merged.content) : conflict2(path2, ownership, merged.conflict || "\u4E09\u65B9\u5408\u5E76\u5931\u8D25", expected);
|
|
8585
|
+
}
|
|
8586
|
+
return conflict2(path2, ownership, "\u5BA2\u6237\u548C Windy \u5747\u5DF2\u4FEE\u6539", expected);
|
|
8587
|
+
}
|
|
8588
|
+
function shouldForcePlatformBaseline(input) {
|
|
8589
|
+
return input.forcePlatformBaseline && !input.oursChanged && input.base !== undefined && hashContent(input.base) !== input.sourceArtifactHash;
|
|
8590
|
+
}
|
|
8591
|
+
function targetChangedSinceBase(sourceHash, base, target) {
|
|
8592
|
+
return base && target ? hashContent(base) !== hashContent(target) : !!target && hashContent(target) !== sourceHash;
|
|
8593
|
+
}
|
|
8594
|
+
function readyPlannedFile(path2, ownership, action, reason, expectedProjectContent, content) {
|
|
8595
|
+
return {
|
|
8596
|
+
path: path2,
|
|
8597
|
+
ownership,
|
|
8598
|
+
action,
|
|
8599
|
+
reason,
|
|
8600
|
+
expectedProjectContent,
|
|
8601
|
+
content
|
|
8602
|
+
};
|
|
8603
|
+
}
|
|
8604
|
+
function expectationForSnapshot(content) {
|
|
8605
|
+
return content ? { kind: "hash", value: hashContent(content) } : { kind: "missing" };
|
|
8606
|
+
}
|
|
8607
|
+
function conflict2(path2, ownership, reason, expectedProjectContent) {
|
|
8608
|
+
return {
|
|
8609
|
+
path: path2,
|
|
8610
|
+
ownership,
|
|
8611
|
+
action: "preserve",
|
|
8612
|
+
reason: "\u5BA2\u6237\u5DF2\u4FEE\u6539",
|
|
8613
|
+
expectedProjectContent,
|
|
8614
|
+
conflict: reason
|
|
8615
|
+
};
|
|
8616
|
+
}
|
|
8617
|
+
async function readOptional(path2) {
|
|
8618
|
+
try {
|
|
8619
|
+
return await readFile12(path2);
|
|
8620
|
+
} catch (error) {
|
|
8621
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
8622
|
+
return;
|
|
8623
|
+
}
|
|
8624
|
+
throw error;
|
|
8625
|
+
}
|
|
8626
|
+
}
|
|
8627
|
+
|
|
8628
|
+
// src/update/planner-snapshots.ts
|
|
8629
|
+
async function snapshotProjectFiles(project, paths, seed) {
|
|
8630
|
+
const snapshots = new Map(seed);
|
|
8631
|
+
await Promise.all([...paths].filter((path2) => !snapshots.has(path2)).map(async (path2) => {
|
|
8632
|
+
snapshots.set(path2, await readOptional2(join18(project, path2)));
|
|
8633
|
+
}));
|
|
8634
|
+
return snapshots;
|
|
8635
|
+
}
|
|
8636
|
+
async function snapshotProjectModes(project, paths) {
|
|
8637
|
+
return new Map(await Promise.all([...paths].map(async (path2) => [path2, await readModeOptional(join18(project, path2))])));
|
|
8638
|
+
}
|
|
8639
|
+
async function assertPlanSnapshots(project, expectations) {
|
|
8640
|
+
const changed = [];
|
|
8641
|
+
for (const expectation2 of expectations) {
|
|
8642
|
+
const actual = expectationForSnapshot(await readOptional2(join18(project, expectation2.path)));
|
|
8643
|
+
if (JSON.stringify(actual) !== JSON.stringify(expectation2.expected)) {
|
|
8644
|
+
changed.push(expectation2.path);
|
|
8645
|
+
continue;
|
|
8646
|
+
}
|
|
8647
|
+
if ("mode" in expectation2 && await readModeOptional(join18(project, expectation2.path)) !== expectation2.mode) {
|
|
8648
|
+
changed.push(expectation2.path);
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
if (changed.length) {
|
|
8652
|
+
throw new Error(`\u751F\u6210\u66F4\u65B0\u8BA1\u5212\u671F\u95F4\u5019\u9009\u6587\u4EF6\u53D1\u751F\u53D8\u5316\uFF1A${changed.join("\u3001")}`);
|
|
8653
|
+
}
|
|
8654
|
+
}
|
|
8655
|
+
async function readModeOptional(path2) {
|
|
8656
|
+
try {
|
|
8657
|
+
return (await stat2(path2)).mode & 511;
|
|
8658
|
+
} catch (error) {
|
|
8659
|
+
if (isMissing5(error))
|
|
8660
|
+
return;
|
|
8661
|
+
throw error;
|
|
8662
|
+
}
|
|
8663
|
+
}
|
|
8664
|
+
async function readSourceParentMode(path2) {
|
|
8665
|
+
try {
|
|
8666
|
+
const info = await stat2(path2);
|
|
8667
|
+
if (!info.isDirectory()) {
|
|
8668
|
+
throw new Error(`artifact \u6E90\u7801\u7236\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\uFF1A${path2}`);
|
|
8669
|
+
}
|
|
8670
|
+
return info.mode & 493 | 493;
|
|
8671
|
+
} catch (error) {
|
|
8672
|
+
if (isMissing5(error))
|
|
8673
|
+
return 493;
|
|
8674
|
+
throw error;
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
async function readOptional2(path2) {
|
|
8678
|
+
try {
|
|
8679
|
+
return await readFile13(path2);
|
|
8680
|
+
} catch (error) {
|
|
8681
|
+
if (isMissing5(error))
|
|
8682
|
+
return;
|
|
8683
|
+
throw error;
|
|
8684
|
+
}
|
|
8685
|
+
}
|
|
8686
|
+
function isMissing5(error) {
|
|
8687
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
8688
|
+
}
|
|
8689
|
+
|
|
8690
|
+
// src/update/docker-copy-legacy-migration.ts
|
|
8691
|
+
async function migrateDockerfileCopySource(source, target, conflict3, locations) {
|
|
8692
|
+
const decoded = new TextDecoder().decode(source);
|
|
8693
|
+
const legacy = !hasDockerfileCopyAnchors(decoded, target);
|
|
8694
|
+
if (!hasSinglePublishedBunInstallInstruction(decoded)) {
|
|
8695
|
+
return {
|
|
8696
|
+
legacy,
|
|
8697
|
+
conflict: "Bun \u5B89\u88C5\u547D\u4EE4\u4E0D\u662F\u53EF\u8BC1\u660E\u7684 Windy \u5DF2\u53D1\u5E03\u6807\u51C6"
|
|
8698
|
+
};
|
|
8699
|
+
}
|
|
8700
|
+
if (!legacy)
|
|
8701
|
+
return { content: decoded, legacy };
|
|
8702
|
+
if (!conflict3) {
|
|
8703
|
+
return {
|
|
8704
|
+
content: adoptLegacyCopyAnchors(decoded, target),
|
|
8705
|
+
legacy
|
|
8706
|
+
};
|
|
8707
|
+
}
|
|
8708
|
+
const [base, theirs] = await Promise.all([
|
|
8709
|
+
readOptional2(join19(locations.baseDirectory, locations.path)),
|
|
8710
|
+
readOptional2(join19(locations.targetDirectory, locations.path))
|
|
8711
|
+
]);
|
|
8712
|
+
if (!base || !theirs)
|
|
8713
|
+
return { legacy };
|
|
8714
|
+
const merged = await mergeLegacyDockerfile(base, source, theirs, target);
|
|
8715
|
+
return {
|
|
8716
|
+
content: merged.content,
|
|
8717
|
+
conflict: merged.conflict,
|
|
8718
|
+
legacy
|
|
8719
|
+
};
|
|
8720
|
+
}
|
|
8721
|
+
async function mergeLegacyDockerfile(base, ours, theirs, target) {
|
|
8722
|
+
const normalized = [base, ours, theirs].map((content2) => normalizeDockerfileForMerge(new TextDecoder().decode(content2), target));
|
|
8723
|
+
if (normalized.some((content2) => content2 === undefined))
|
|
8724
|
+
return {};
|
|
8725
|
+
const encode = (content2) => new TextEncoder().encode(content2);
|
|
8726
|
+
const merged = await mergeTextFile(encode(normalized[0]), encode(normalized[1]), encode(normalized[2]));
|
|
8727
|
+
if (!merged.content)
|
|
8728
|
+
return { conflict: merged.conflict };
|
|
8729
|
+
const content = restoreNormalizedDockerfile(new TextDecoder().decode(merged.content), target);
|
|
8730
|
+
return content === undefined ? {} : { content };
|
|
8731
|
+
}
|
|
8732
|
+
var installPlaceholder = "# windy-merge:bun-install";
|
|
8733
|
+
function normalizeDockerfileForMerge(source, target) {
|
|
8734
|
+
let normalized = hasDockerfileCopyAnchors(source, target) ? source : addLegacyCopyAnchors(source, target);
|
|
8735
|
+
if (normalized === undefined)
|
|
8736
|
+
return;
|
|
8737
|
+
for (const anchor of dockerAnchors(target)) {
|
|
8738
|
+
const next = replaceAnchoredSection(normalized, anchor, [
|
|
8739
|
+
`# windy-merge:${anchor}`
|
|
8740
|
+
]);
|
|
8741
|
+
if (next === undefined)
|
|
8742
|
+
return;
|
|
8743
|
+
normalized = next;
|
|
8744
|
+
}
|
|
8745
|
+
return replaceUniqueLine(normalized, isPublishedBunInstallInstruction, installPlaceholder);
|
|
8746
|
+
}
|
|
8747
|
+
function restoreNormalizedDockerfile(source, target) {
|
|
8748
|
+
let restored = source;
|
|
8749
|
+
for (const anchor of dockerAnchors(target)) {
|
|
8750
|
+
const next = replaceAnchoredSection(restored, anchor, []);
|
|
8751
|
+
if (next === undefined)
|
|
8752
|
+
return;
|
|
8753
|
+
restored = next;
|
|
8754
|
+
}
|
|
8755
|
+
return replaceUniqueLine(restored, (line) => line === installPlaceholder, dockerBunInstallInstruction);
|
|
8756
|
+
}
|
|
8757
|
+
function dockerAnchors(target) {
|
|
8758
|
+
return target.includeBuildClosure ? [depsCopyAnchor, buildCopyAnchor] : [depsCopyAnchor];
|
|
8759
|
+
}
|
|
8760
|
+
function adoptLegacyCopyAnchors(source, target) {
|
|
8761
|
+
const anchored = addLegacyCopyAnchors(source, target);
|
|
8762
|
+
return anchored === undefined ? undefined : replaceUniqueLine(anchored, isPublishedBunInstallInstruction, dockerBunInstallInstruction);
|
|
8763
|
+
}
|
|
8764
|
+
function addLegacyCopyAnchors(source, target) {
|
|
8765
|
+
const deps = wrapLegacyCopySection(source, "COPY package.json bun.lock ./", isPublishedBunInstallInstruction, depsCopyAnchor, (line) => /^COPY ((?:apps|packages)\/[^/\s]+\/package\.json) \1$/.test(line), `COPY ${target.entryDirectory}/package.json ${target.entryDirectory}/package.json`);
|
|
8766
|
+
if (deps === undefined)
|
|
8767
|
+
return;
|
|
8768
|
+
if (!target.includeBuildClosure)
|
|
8769
|
+
return deps;
|
|
8770
|
+
return wrapLegacyCopySection(deps, "FROM deps AS build", (line) => line === "RUN bun run --cwd apps/web build", buildCopyAnchor, (line) => /^COPY ((?:apps|packages)\/[^/\s]+) \1$/.test(line), `COPY ${target.entryDirectory} ${target.entryDirectory}`);
|
|
8771
|
+
}
|
|
8772
|
+
var publishedBunInstallInstructions = new Set([
|
|
8773
|
+
"RUN bun install --frozen-lockfile",
|
|
8774
|
+
"RUN --mount=type=cache,target=/root/.bun/install/cache,sharing=locked bun install --frozen-lockfile",
|
|
8775
|
+
dockerBunInstallInstruction
|
|
8776
|
+
]);
|
|
8777
|
+
function hasSinglePublishedBunInstallInstruction(source) {
|
|
8778
|
+
const candidates = source.split(`
|
|
8779
|
+
`).filter((line) => line.startsWith("RUN ") && (/\bbun\s+install\b/.test(line) || line.includes('Bun.spawn(["bun","install"')));
|
|
8780
|
+
return candidates.length === 1 && isPublishedBunInstallInstruction(candidates[0]);
|
|
8781
|
+
}
|
|
8782
|
+
function isPublishedBunInstallInstruction(line) {
|
|
8783
|
+
return publishedBunInstallInstructions.has(line);
|
|
8784
|
+
}
|
|
8785
|
+
function wrapLegacyCopySection(source, startLine, isEnd, anchor, isCopy, requiredCopy) {
|
|
8786
|
+
const lines = source.split(`
|
|
8787
|
+
`);
|
|
8788
|
+
const starts = indexesOf(lines, (line) => line === startLine);
|
|
8789
|
+
const ends = indexesOf(lines, isEnd);
|
|
8790
|
+
if (starts.length !== 1 || ends.length !== 1 || source.includes(`# ${anchor} begin`) || source.includes(`# ${anchor} end`)) {
|
|
8791
|
+
return;
|
|
8792
|
+
}
|
|
8793
|
+
const start = starts[0];
|
|
8794
|
+
const end = ends[0];
|
|
8795
|
+
if (end <= start)
|
|
8796
|
+
return;
|
|
8797
|
+
const section = lines.slice(start + 1, end);
|
|
8798
|
+
const copyLines = section.filter((line) => line.length > 0);
|
|
8799
|
+
if (copyLines.length === 0 || !copyLines.every(isCopy) || copyLines.filter((line) => line === requiredCopy).length !== 1) {
|
|
8800
|
+
return;
|
|
8801
|
+
}
|
|
8802
|
+
return [
|
|
8803
|
+
...lines.slice(0, start + 1),
|
|
8804
|
+
`# ${anchor} begin`,
|
|
8805
|
+
...section,
|
|
8806
|
+
`# ${anchor} end`,
|
|
8807
|
+
...lines.slice(end)
|
|
8808
|
+
].join(`
|
|
8809
|
+
`);
|
|
8810
|
+
}
|
|
8811
|
+
function replaceUniqueLine(source, predicate, replacement) {
|
|
8812
|
+
const lines = source.split(`
|
|
8813
|
+
`);
|
|
8814
|
+
const matches2 = indexesOf(lines, predicate);
|
|
8815
|
+
if (matches2.length !== 1)
|
|
8816
|
+
return;
|
|
8817
|
+
lines[matches2[0]] = replacement;
|
|
8818
|
+
return lines.join(`
|
|
8819
|
+
`);
|
|
8820
|
+
}
|
|
8821
|
+
function replaceAnchoredSection(source, anchor, content) {
|
|
8822
|
+
const lines = source.split(`
|
|
8823
|
+
`);
|
|
8824
|
+
const begins = indexesOf(lines, (line) => line === `# ${anchor} begin`);
|
|
8825
|
+
const ends = indexesOf(lines, (line) => line === `# ${anchor} end`);
|
|
8826
|
+
if (begins.length !== 1 || ends.length !== 1 || ends[0] <= begins[0]) {
|
|
8827
|
+
return;
|
|
8828
|
+
}
|
|
8829
|
+
return [
|
|
8830
|
+
...lines.slice(0, begins[0] + 1),
|
|
8831
|
+
...content,
|
|
8832
|
+
...lines.slice(ends[0])
|
|
8833
|
+
].join(`
|
|
8834
|
+
`);
|
|
8835
|
+
}
|
|
8836
|
+
function indexesOf(lines, predicate) {
|
|
8837
|
+
return lines.flatMap((line, index) => predicate(line) ? [index] : []);
|
|
8838
|
+
}
|
|
8839
|
+
|
|
8840
|
+
// src/update/docker-copy-update-plan.ts
|
|
8841
|
+
async function deriveDockerfilePlans(files, snapshots, paths, targetWorkspaceManifests, baseDirectory, targetDirectory) {
|
|
8842
|
+
const rootPackage = virtualPathContent("package.json", files, snapshots, targetWorkspaceManifests);
|
|
8843
|
+
if (!rootPackage)
|
|
8844
|
+
return;
|
|
8845
|
+
const rootManifest = parsePackage(rootPackage, "package.json");
|
|
8846
|
+
const workspacePatterns = rootManifest.workspaces ?? [];
|
|
8847
|
+
const packages = [];
|
|
8848
|
+
for (const path2 of [
|
|
8849
|
+
...new Set([...paths, ...targetWorkspaceManifests.keys()])
|
|
8850
|
+
].filter((path3) => path3.endsWith("package.json"))) {
|
|
8851
|
+
const directory = path2 === "package.json" ? "." : path2.slice(0, -13);
|
|
8852
|
+
if (directory !== "." && !workspacePatterns.some((pattern) => matchesWorkspace(pattern, directory))) {
|
|
8853
|
+
continue;
|
|
8854
|
+
}
|
|
8855
|
+
const content = virtualPathContent(path2, files, snapshots, targetWorkspaceManifests);
|
|
8856
|
+
if (!content)
|
|
8857
|
+
continue;
|
|
8858
|
+
const manifest = parsePackage(content, path2);
|
|
8859
|
+
packages.push({
|
|
8860
|
+
name: manifest.name,
|
|
8861
|
+
directory,
|
|
8862
|
+
dependencies: [
|
|
8863
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
8864
|
+
...Object.keys(manifest.devDependencies ?? {})
|
|
8865
|
+
],
|
|
8866
|
+
workspaceDependencies: Object.entries({
|
|
8867
|
+
...manifest.dependencies,
|
|
8868
|
+
...manifest.devDependencies
|
|
8869
|
+
}).filter(([, range]) => range.startsWith("workspace:")).map(([name]) => name)
|
|
8870
|
+
});
|
|
8871
|
+
}
|
|
8872
|
+
for (const target of dockerfileCopyTargets) {
|
|
8873
|
+
const dockerPlan = files.find(({ path: path2 }) => path2 === target.dockerfilePath);
|
|
8874
|
+
if (!dockerPlan)
|
|
8875
|
+
continue;
|
|
8876
|
+
const dockerSource = dockerPlan.conflict ? snapshots.get(dockerPlan.path) : virtualContent(dockerPlan, snapshots);
|
|
8877
|
+
if (!dockerSource)
|
|
8878
|
+
continue;
|
|
8879
|
+
const source = new TextDecoder().decode(dockerSource);
|
|
8880
|
+
const migration = await migrateDockerfileCopySource(dockerSource, target, Boolean(dockerPlan.conflict), {
|
|
8881
|
+
path: dockerPlan.path,
|
|
8882
|
+
baseDirectory,
|
|
8883
|
+
targetDirectory
|
|
8884
|
+
});
|
|
8885
|
+
if (migration.conflict)
|
|
8886
|
+
dockerPlan.conflict = migration.conflict;
|
|
8887
|
+
if (migration.content === undefined || dockerPlan.conflict && !migration.legacy) {
|
|
8888
|
+
continue;
|
|
8889
|
+
}
|
|
8890
|
+
const next = syncDockerfileSource(migration.content, target, packages);
|
|
8891
|
+
if (next === source && !dockerPlan.conflict)
|
|
8892
|
+
continue;
|
|
8893
|
+
dockerPlan.action = "update";
|
|
8894
|
+
dockerPlan.reason = migration.legacy ? "\u7ED3\u6784\u5316\u91C7\u7EB3\u65E7 Dockerfile \u5E76\u540C\u6B65\u5BA2\u6237 workspace COPY \u6E05\u5355" : "\u6309\u4E8B\u52A1\u5B89\u5168 workspace \u89C6\u56FE\u540C\u6B65 Docker COPY \u6E05\u5355";
|
|
8895
|
+
dockerPlan.content = new TextEncoder().encode(next);
|
|
8896
|
+
delete dockerPlan.conflict;
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
function virtualPathContent(path2, files, snapshots, targetWorkspaceManifests) {
|
|
8900
|
+
const plan = files.find((item) => item.path === path2);
|
|
8901
|
+
return plan ? virtualContent(plan, snapshots) : snapshots.get(path2) ?? targetWorkspaceManifests.get(path2);
|
|
8902
|
+
}
|
|
8903
|
+
function virtualContent(plan, snapshots) {
|
|
8904
|
+
if (plan.action === "delete")
|
|
8905
|
+
return;
|
|
8906
|
+
if (plan.action === "create" || plan.action === "update")
|
|
8907
|
+
return plan.content;
|
|
8908
|
+
return snapshots.get(plan.path);
|
|
8909
|
+
}
|
|
8910
|
+
function parsePackage(content, path2) {
|
|
8911
|
+
try {
|
|
8912
|
+
return JSON.parse(new TextDecoder().decode(content));
|
|
8913
|
+
} catch (error) {
|
|
8914
|
+
throw new Error(`\u65E0\u6CD5\u89E3\u6790 Docker workspace \u8F93\u5165\uFF1A${path2}`, {
|
|
8915
|
+
cause: error
|
|
8916
|
+
});
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
function matchesWorkspace(pattern, directory) {
|
|
8920
|
+
return pattern.endsWith("/*") && directory.startsWith(pattern.slice(0, -1));
|
|
8921
|
+
}
|
|
8922
|
+
|
|
8923
|
+
// src/update/migration-reconciler.ts
|
|
8924
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
8925
|
+
import { join as join21 } from "path";
|
|
8926
|
+
|
|
8927
|
+
// src/update/migration-project-file.ts
|
|
8928
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
8929
|
+
import { join as join20 } from "path";
|
|
8930
|
+
async function readProjectJson(input, path2) {
|
|
8931
|
+
const content = await readProjectFile(input, path2);
|
|
8932
|
+
if (!content) {
|
|
8933
|
+
const error = new Error(`\u5F53\u524D\u9879\u76EE\u6587\u4EF6\u7F3A\u5931\uFF1A${path2}`);
|
|
8934
|
+
error.code = "ENOENT";
|
|
8935
|
+
throw error;
|
|
8936
|
+
}
|
|
8937
|
+
return JSON.parse(new TextDecoder().decode(content));
|
|
8938
|
+
}
|
|
8939
|
+
async function readProjectFile(input, path2) {
|
|
8940
|
+
if (input.projectSnapshots?.has(path2)) {
|
|
8941
|
+
return input.projectSnapshots.get(path2);
|
|
8942
|
+
}
|
|
8943
|
+
let content;
|
|
8944
|
+
try {
|
|
8945
|
+
content = await readFile14(join20(input.projectDirectory, path2));
|
|
8946
|
+
} catch (error) {
|
|
8947
|
+
if (!isMissingFileError(error))
|
|
8948
|
+
throw error;
|
|
8949
|
+
}
|
|
8950
|
+
if (input.projectSnapshots) {
|
|
8951
|
+
input.projectSnapshots.set(path2, content);
|
|
8952
|
+
}
|
|
8953
|
+
return content;
|
|
8954
|
+
}
|
|
8955
|
+
function isMissingFileError(error) {
|
|
8956
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
8957
|
+
}
|
|
8958
|
+
|
|
8959
|
+
// src/update/snapshot-delta.ts
|
|
8960
|
+
function applySnapshotDelta(before, after, current, path2, conflicts, conflictPath) {
|
|
8961
|
+
if (deepEqual(before, after))
|
|
8962
|
+
return structuredClone(current);
|
|
8963
|
+
if (!isRecord(before) || !isRecord(after) || !isRecord(current)) {
|
|
8964
|
+
if (deepEqual(current, before) || deepEqual(current, after)) {
|
|
8965
|
+
return structuredClone(after);
|
|
8966
|
+
}
|
|
8967
|
+
conflicts.push(`${conflictPath}\uFF1A\u5E73\u53F0\u4E0E\u4E1A\u52A1\u540C\u65F6\u4FEE\u6539 snapshot \u8282\u70B9 ${path2.join(".") || "<root>"}`);
|
|
8968
|
+
return structuredClone(current);
|
|
8969
|
+
}
|
|
8970
|
+
const merged = structuredClone(current);
|
|
8971
|
+
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
8972
|
+
for (const key of keys) {
|
|
8973
|
+
if (path2.length === 0 && (key === "id" || key === "prevId"))
|
|
8974
|
+
continue;
|
|
8975
|
+
const nextPath = [...path2, key];
|
|
8976
|
+
if (!(key in after)) {
|
|
8977
|
+
if (!(key in current))
|
|
8978
|
+
continue;
|
|
8979
|
+
if (deepEqual(current[key], before[key]))
|
|
8980
|
+
delete merged[key];
|
|
8981
|
+
else
|
|
8982
|
+
conflicts.push(`${conflictPath}\uFF1A\u5E73\u53F0\u5220\u9664\u4E86\u4E1A\u52A1\u5DF2\u4FEE\u6539\u7684 snapshot \u8282\u70B9 ${nextPath.join(".")}`);
|
|
8983
|
+
continue;
|
|
8984
|
+
}
|
|
8985
|
+
if (!(key in before)) {
|
|
8986
|
+
if (!(key in current) || deepEqual(current[key], after[key])) {
|
|
8987
|
+
merged[key] = structuredClone(after[key]);
|
|
8988
|
+
} else {
|
|
8989
|
+
conflicts.push(`${conflictPath}\uFF1A\u5E73\u53F0\u65B0\u589E\u7684 snapshot \u8282\u70B9\u5DF2\u88AB\u4E1A\u52A1\u5360\u7528 ${nextPath.join(".")}`);
|
|
8990
|
+
}
|
|
8991
|
+
continue;
|
|
8992
|
+
}
|
|
8993
|
+
merged[key] = applySnapshotDelta(before[key], after[key], current[key], nextPath, conflicts, conflictPath);
|
|
8994
|
+
}
|
|
8995
|
+
return merged;
|
|
8996
|
+
}
|
|
8997
|
+
function isRecord(value) {
|
|
8998
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8999
|
+
}
|
|
9000
|
+
function deepEqual(left, right) {
|
|
9001
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
9002
|
+
}
|
|
9003
|
+
|
|
9004
|
+
// src/update/migration-reconciler.ts
|
|
9005
|
+
var drizzleRoot = "packages/database/drizzle";
|
|
9006
|
+
var journalPath = `${drizzleRoot}/meta/_journal.json`;
|
|
9007
|
+
var migrationPattern = /^(\d{4})_(.+)$/;
|
|
9008
|
+
async function reconcileDrizzleMigrations(input) {
|
|
9009
|
+
const journals = await readJournals(input);
|
|
9010
|
+
if (!journals) {
|
|
9011
|
+
return unchanged(input.targetManifest);
|
|
8878
9012
|
}
|
|
8879
|
-
|
|
8880
|
-
|
|
9013
|
+
const { base, project, target } = journals;
|
|
9014
|
+
const conflicts = [];
|
|
9015
|
+
validateUniqueNames(base, "\u6765\u6E90\u6A21\u677F", conflicts);
|
|
9016
|
+
validateUniqueNames(project, "\u5F53\u524D\u9879\u76EE", conflicts);
|
|
9017
|
+
validateUniqueNames(target, "\u76EE\u6807\u6A21\u677F", conflicts);
|
|
9018
|
+
if (conflicts.length) {
|
|
9019
|
+
return { ...unchanged(input.targetManifest), conflicts };
|
|
8881
9020
|
}
|
|
8882
|
-
|
|
8883
|
-
|
|
9021
|
+
const baseNames = new Set(base.entries.map(({ tag }) => stableName(tag)));
|
|
9022
|
+
const projectNames = new Set(project.entries.map(({ tag }) => stableName(tag)));
|
|
9023
|
+
const managedNames = new Set(input.sourceManifest.files.filter(({ path: path2 }) => isSqlMigration(path2)).map(({ path: path2 }) => stableName(path2.slice(drizzleRoot.length + 1, -4))));
|
|
9024
|
+
const candidates = target.entries.filter(({ tag }) => !baseNames.has(stableName(tag)));
|
|
9025
|
+
for (const { tag } of candidates) {
|
|
9026
|
+
const name = stableName(tag);
|
|
9027
|
+
if (projectNames.has(name) && !managedNames.has(name)) {
|
|
9028
|
+
conflicts.push(`${journalPath}\uFF1A\u5E73\u53F0 migration \u540D\u79F0\u4E0E\u4E0B\u6E38\u4E1A\u52A1\u5386\u53F2\u51B2\u7A81 ${name}`);
|
|
9029
|
+
}
|
|
8884
9030
|
}
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
9031
|
+
const additions = candidates.filter(({ tag }) => !projectNames.has(stableName(tag)));
|
|
9032
|
+
const contentOverrides = new Map;
|
|
9033
|
+
const files = await preservePublishedHistory(input.sourceManifest, input, contentOverrides);
|
|
9034
|
+
const publishedPaths = new Set(files.map(({ path: path2 }) => path2));
|
|
9035
|
+
files.push(...input.targetManifest.files.filter(({ path: path2 }) => !isDrizzleHistoryPath(path2)));
|
|
9036
|
+
if (!additions.length) {
|
|
9037
|
+
const journalContent2 = encodeJson(project);
|
|
9038
|
+
contentOverrides.set(journalPath, journalContent2);
|
|
9039
|
+
files.push(managedEntry(journalPath, journalContent2));
|
|
9040
|
+
return result(input.targetManifest, files, contentOverrides, publishedPaths, conflicts);
|
|
8888
9041
|
}
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
9042
|
+
let nextPrefix = highestPrefix(project) + 1;
|
|
9043
|
+
let nextIndex = highestIndex(project) + 1;
|
|
9044
|
+
let previousWhen = project.entries.at(-1)?.when ?? 0;
|
|
9045
|
+
let currentSnapshot = await readLatestSnapshot(input, project);
|
|
9046
|
+
const mergedJournal = structuredClone(project);
|
|
9047
|
+
for (const addition of additions) {
|
|
9048
|
+
const targetPosition = target.entries.indexOf(addition);
|
|
9049
|
+
const previousTarget = target.entries[targetPosition - 1];
|
|
9050
|
+
if (!previousTarget) {
|
|
9051
|
+
conflicts.push(`${journalPath}\uFF1A\u76EE\u6807 migration \u7F3A\u5C11\u524D\u7F6E\u5FEB\u7167`);
|
|
9052
|
+
break;
|
|
9053
|
+
}
|
|
9054
|
+
const targetBefore = await readSnapshot(input.targetDirectory, previousTarget.tag);
|
|
9055
|
+
const targetAfter = await readSnapshot(input.targetDirectory, addition.tag);
|
|
9056
|
+
const merged = applySnapshotDelta(targetBefore, targetAfter, currentSnapshot, [], conflicts, journalPath);
|
|
9057
|
+
if (conflicts.length)
|
|
9058
|
+
break;
|
|
9059
|
+
const prefix = String(nextPrefix).padStart(4, "0");
|
|
9060
|
+
const name = stableName(addition.tag);
|
|
9061
|
+
const tag = `${prefix}_${name}`;
|
|
9062
|
+
const sqlPath = `${drizzleRoot}/${tag}.sql`;
|
|
9063
|
+
const snapshotPath = `${drizzleRoot}/meta/${prefix}_snapshot.json`;
|
|
9064
|
+
const sql = await readFile15(join21(input.targetDirectory, `${drizzleRoot}/${addition.tag}.sql`));
|
|
9065
|
+
merged.id = targetAfter.id;
|
|
9066
|
+
merged.prevId = currentSnapshot.id;
|
|
9067
|
+
const snapshot = encodeJson(merged);
|
|
9068
|
+
contentOverrides.set(sqlPath, sql);
|
|
9069
|
+
contentOverrides.set(snapshotPath, snapshot);
|
|
9070
|
+
files.push(managedEntry(sqlPath, sql), managedEntry(snapshotPath, snapshot));
|
|
9071
|
+
previousWhen = Math.max(previousWhen + 1, addition.when);
|
|
9072
|
+
mergedJournal.entries.push({
|
|
9073
|
+
...addition,
|
|
9074
|
+
idx: nextIndex,
|
|
9075
|
+
when: previousWhen,
|
|
9076
|
+
tag
|
|
9077
|
+
});
|
|
9078
|
+
currentSnapshot = merged;
|
|
9079
|
+
nextPrefix += 1;
|
|
9080
|
+
nextIndex += 1;
|
|
9081
|
+
}
|
|
9082
|
+
const journalContent = encodeJson(mergedJournal);
|
|
9083
|
+
contentOverrides.set(journalPath, journalContent);
|
|
9084
|
+
files.push(managedEntry(journalPath, journalContent));
|
|
9085
|
+
return result(input.targetManifest, files, contentOverrides, publishedPaths, conflicts);
|
|
8896
9086
|
}
|
|
8897
|
-
function
|
|
9087
|
+
function result(targetManifest, files, contentOverrides, forcePreserves, conflicts) {
|
|
8898
9088
|
return {
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
9089
|
+
targetManifest: {
|
|
9090
|
+
...targetManifest,
|
|
9091
|
+
files: deduplicate(files).sort((left, right) => left.path.localeCompare(right.path))
|
|
9092
|
+
},
|
|
9093
|
+
contentOverrides,
|
|
9094
|
+
forceUpdates: new Set([journalPath]),
|
|
9095
|
+
forcePreserves,
|
|
9096
|
+
conflicts
|
|
8905
9097
|
};
|
|
8906
9098
|
}
|
|
8907
|
-
function
|
|
8908
|
-
return content ? { kind: "hash", value: hashContent(content) } : { kind: "missing" };
|
|
8909
|
-
}
|
|
8910
|
-
function conflict2(path2, ownership, reason, expectedProjectContent) {
|
|
9099
|
+
function unchanged(targetManifest) {
|
|
8911
9100
|
return {
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
conflict: reason
|
|
9101
|
+
targetManifest,
|
|
9102
|
+
contentOverrides: new Map,
|
|
9103
|
+
forceUpdates: new Set,
|
|
9104
|
+
forcePreserves: new Set,
|
|
9105
|
+
conflicts: []
|
|
8918
9106
|
};
|
|
8919
9107
|
}
|
|
8920
|
-
async function
|
|
9108
|
+
async function readJournals(input) {
|
|
8921
9109
|
try {
|
|
8922
|
-
|
|
9110
|
+
const [base, project, target] = await Promise.all([
|
|
9111
|
+
readJson(join21(input.baseDirectory, journalPath)),
|
|
9112
|
+
readProjectJson(input, journalPath),
|
|
9113
|
+
readJson(join21(input.targetDirectory, journalPath))
|
|
9114
|
+
]);
|
|
9115
|
+
return { base, project, target };
|
|
8923
9116
|
} catch (error) {
|
|
8924
|
-
if (error
|
|
9117
|
+
if (isMissingFileError(error))
|
|
8925
9118
|
return;
|
|
8926
|
-
}
|
|
8927
9119
|
throw error;
|
|
8928
9120
|
}
|
|
8929
9121
|
}
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
snapshots.set(path2, await readOptional2(join20(project, path2)));
|
|
9122
|
+
async function preservePublishedHistory(manifest, input, overrides) {
|
|
9123
|
+
const entries = manifest.files.filter(({ path: path2 }) => isMigrationArtifact(path2) && path2 !== journalPath);
|
|
9124
|
+
await Promise.all(entries.map(async ({ path: path2 }) => {
|
|
9125
|
+
const content = await readProjectFile(input, path2);
|
|
9126
|
+
if (!content)
|
|
9127
|
+
throw new Error(`\u5F53\u524D\u9879\u76EE migration \u6587\u4EF6\u7F3A\u5931\uFF1A${path2}`);
|
|
9128
|
+
overrides.set(path2, content);
|
|
8938
9129
|
}));
|
|
8939
|
-
return
|
|
8940
|
-
}
|
|
8941
|
-
async function snapshotProjectModes(project, paths) {
|
|
8942
|
-
return new Map(await Promise.all([...paths].map(async (path2) => [path2, await readModeOptional(join20(project, path2))])));
|
|
9130
|
+
return entries;
|
|
8943
9131
|
}
|
|
8944
|
-
async function
|
|
8945
|
-
const
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
}
|
|
8952
|
-
if ("mode" in expectation2 && await readModeOptional(join20(project, expectation2.path)) !== expectation2.mode) {
|
|
8953
|
-
changed.push(expectation2.path);
|
|
8954
|
-
}
|
|
9132
|
+
async function readLatestSnapshot(input, journal) {
|
|
9133
|
+
const latest = journal.entries.at(-1);
|
|
9134
|
+
if (!latest)
|
|
9135
|
+
throw new Error(`${journalPath}\uFF1A\u5F53\u524D\u9879\u76EE\u6CA1\u6709 migration`);
|
|
9136
|
+
const match = migrationPattern.exec(latest.tag);
|
|
9137
|
+
if (!match) {
|
|
9138
|
+
throw new Error(`${journalPath}\uFF1Amigration tag \u683C\u5F0F\u65E0\u6548\uFF1A${latest.tag}`);
|
|
8955
9139
|
}
|
|
8956
|
-
|
|
8957
|
-
|
|
9140
|
+
const snapshotPath = `${drizzleRoot}/meta/${match[1]}_snapshot.json`;
|
|
9141
|
+
const content = await readProjectFile(input, snapshotPath);
|
|
9142
|
+
if (!content) {
|
|
9143
|
+
throw new Error(`\u5F53\u524D\u9879\u76EE migration \u6587\u4EF6\u7F3A\u5931\uFF1A${snapshotPath}`);
|
|
8958
9144
|
}
|
|
9145
|
+
return JSON.parse(new TextDecoder().decode(content));
|
|
8959
9146
|
}
|
|
8960
|
-
async function
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
return;
|
|
8966
|
-
throw error;
|
|
8967
|
-
}
|
|
9147
|
+
async function readSnapshot(root, tag) {
|
|
9148
|
+
const match = migrationPattern.exec(tag);
|
|
9149
|
+
if (!match)
|
|
9150
|
+
throw new Error(`${journalPath}\uFF1Amigration tag \u683C\u5F0F\u65E0\u6548\uFF1A${tag}`);
|
|
9151
|
+
return await readJson(join21(root, `${drizzleRoot}/meta/${match[1]}_snapshot.json`));
|
|
8968
9152
|
}
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
9153
|
+
function validateUniqueNames(journal, label, conflicts) {
|
|
9154
|
+
const seen = new Set;
|
|
9155
|
+
for (const { tag } of journal.entries) {
|
|
9156
|
+
const name = stableName(tag);
|
|
9157
|
+
if (seen.has(name)) {
|
|
9158
|
+
conflicts.push(`${journalPath}\uFF1A${label}\u5B58\u5728\u91CD\u590D migration \u540D\u79F0 ${name}`);
|
|
8974
9159
|
}
|
|
8975
|
-
|
|
8976
|
-
} catch (error) {
|
|
8977
|
-
if (isMissing5(error))
|
|
8978
|
-
return 493;
|
|
8979
|
-
throw error;
|
|
9160
|
+
seen.add(name);
|
|
8980
9161
|
}
|
|
8981
9162
|
}
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
return await readFile15(path2);
|
|
8985
|
-
} catch (error) {
|
|
8986
|
-
if (isMissing5(error))
|
|
8987
|
-
return;
|
|
8988
|
-
throw error;
|
|
8989
|
-
}
|
|
9163
|
+
function stableName(tag) {
|
|
9164
|
+
return migrationPattern.exec(tag)?.[2] ?? tag;
|
|
8990
9165
|
}
|
|
8991
|
-
function
|
|
8992
|
-
return
|
|
9166
|
+
function highestPrefix(journal) {
|
|
9167
|
+
return Math.max(-1, ...journal.entries.map(({ tag }) => Number(migrationPattern.exec(tag)?.[1] ?? -1)));
|
|
9168
|
+
}
|
|
9169
|
+
function highestIndex(journal) {
|
|
9170
|
+
return Math.max(-1, ...journal.entries.map(({ idx }) => idx));
|
|
9171
|
+
}
|
|
9172
|
+
function isDrizzleHistoryPath(path2) {
|
|
9173
|
+
return path2 === journalPath || isMigrationArtifact(path2);
|
|
9174
|
+
}
|
|
9175
|
+
function isMigrationArtifact(path2) {
|
|
9176
|
+
return isSqlMigration(path2) || new RegExp(`^${drizzleRoot}/meta/\\d{4}_snapshot\\.json$`).test(path2);
|
|
9177
|
+
}
|
|
9178
|
+
function isSqlMigration(path2) {
|
|
9179
|
+
return new RegExp(`^${drizzleRoot}/\\d{4}_.+\\.sql$`).test(path2);
|
|
9180
|
+
}
|
|
9181
|
+
function managedEntry(path2, content) {
|
|
9182
|
+
return { path: path2, ownership: "windy-managed", hash: hashContent(content) };
|
|
9183
|
+
}
|
|
9184
|
+
function deduplicate(files) {
|
|
9185
|
+
return [...new Map(files.map((entry) => [entry.path, entry])).values()];
|
|
9186
|
+
}
|
|
9187
|
+
function encodeJson(value) {
|
|
9188
|
+
return new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
|
|
9189
|
+
`);
|
|
9190
|
+
}
|
|
9191
|
+
async function readJson(path2) {
|
|
9192
|
+
return JSON.parse(await readFile15(path2, "utf8"));
|
|
8993
9193
|
}
|
|
8994
9194
|
|
|
8995
9195
|
// src/update/preflight.ts
|
|
@@ -9006,7 +9206,7 @@ async function assertNoPendingUpdate(projectDirectory) {
|
|
|
9006
9206
|
|
|
9007
9207
|
// src/update/reconciled-baselines.ts
|
|
9008
9208
|
import { readFile as readFile16 } from "fs/promises";
|
|
9009
|
-
import { join as
|
|
9209
|
+
import { join as join22 } from "path";
|
|
9010
9210
|
async function carryReconciledBaselines(source, target, projectSnapshots, targetDirectory) {
|
|
9011
9211
|
const sourceByPath = new Map(source.files.map((entry) => [entry.path, entry]));
|
|
9012
9212
|
return {
|
|
@@ -9014,7 +9214,7 @@ async function carryReconciledBaselines(source, target, projectSnapshots, target
|
|
|
9014
9214
|
files: await Promise.all(target.files.map(async (entry) => {
|
|
9015
9215
|
const previous = sourceByPath.get(entry.path);
|
|
9016
9216
|
const project = projectSnapshots.get(entry.path);
|
|
9017
|
-
const targetContent = await readOptional3(
|
|
9217
|
+
const targetContent = await readOptional3(join22(targetDirectory, entry.path));
|
|
9018
9218
|
if (isMissingManagedFile(entry) || !previous || isMissingManagedFile(previous)) {
|
|
9019
9219
|
return entry;
|
|
9020
9220
|
}
|
|
@@ -9093,7 +9293,8 @@ var recipes = [
|
|
|
9093
9293
|
{ id: "starter-0.3.12-to-0.3.13", from: "0.3.12", to: "0.3.13" },
|
|
9094
9294
|
{ id: "starter-0.3.13-to-0.3.14", from: "0.3.13", to: "0.3.14" },
|
|
9095
9295
|
{ id: "starter-0.3.14-to-0.3.15", from: "0.3.14", to: "0.3.15" },
|
|
9096
|
-
{ id: "starter-0.3.15-to-0.3.16", from: "0.3.15", to: "0.3.16" }
|
|
9296
|
+
{ id: "starter-0.3.15-to-0.3.16", from: "0.3.15", to: "0.3.16" },
|
|
9297
|
+
{ id: "starter-0.3.16-to-0.3.17", from: "0.3.16", to: "0.3.17" }
|
|
9097
9298
|
];
|
|
9098
9299
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
9099
9300
|
if (sourceVersion === targetVersion)
|
|
@@ -9130,7 +9331,7 @@ var oxcManagedPaths = new Set([
|
|
|
9130
9331
|
]);
|
|
9131
9332
|
async function createUpdatePlan(request, artifacts) {
|
|
9132
9333
|
await assertProjectReadyForUpdate(request.projectDirectory);
|
|
9133
|
-
const manifestPath =
|
|
9334
|
+
const manifestPath = join23(request.projectDirectory, ".windy/files.json");
|
|
9134
9335
|
const expectedManifestHash = hashContent(await readFile17(manifestPath));
|
|
9135
9336
|
const generationSnapshot = await readGenerationProvenanceSnapshot(request.projectDirectory);
|
|
9136
9337
|
const provenance = generationSnapshot.provenance;
|
|
@@ -9176,8 +9377,8 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
9176
9377
|
const paths = new Set([...sourceEntries.keys(), ...targetEntries.keys()]);
|
|
9177
9378
|
for (const path2 of paths) {
|
|
9178
9379
|
if (!projectSnapshots.has(path2)) {
|
|
9179
|
-
projectSnapshots.set(path2, await readOptional2(
|
|
9180
|
-
projectModes.set(path2, await readModeOptional(
|
|
9380
|
+
projectSnapshots.set(path2, await readOptional2(join23(request.projectDirectory, path2)));
|
|
9381
|
+
projectModes.set(path2, await readModeOptional(join23(request.projectDirectory, path2)));
|
|
9181
9382
|
}
|
|
9182
9383
|
}
|
|
9183
9384
|
const files = await planFiles({
|
|
@@ -9191,18 +9392,18 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
9191
9392
|
manageGeneratedProjectOwned: request.targetModules !== undefined,
|
|
9192
9393
|
forcePlatformBaseline: request.forcePlatformBaseline === true
|
|
9193
9394
|
});
|
|
9194
|
-
deriveDockerfilePlans(files, projectSnapshots, new Set([
|
|
9395
|
+
await deriveDockerfilePlans(files, projectSnapshots, new Set([
|
|
9195
9396
|
...paths,
|
|
9196
9397
|
...workspaceSnapshot.manifests.keys(),
|
|
9197
9398
|
...targetWorkspaceSnapshot.manifests.keys()
|
|
9198
|
-
]), targetWorkspaceSnapshot.manifests);
|
|
9399
|
+
]), targetWorkspaceSnapshot.manifests, artifactSet.baseDirectory, artifactSet.targetDirectory);
|
|
9199
9400
|
for (const file of files) {
|
|
9200
9401
|
file.expectedProjectMode = projectModes.get(file.path);
|
|
9201
9402
|
if (file.action === "create" || file.action === "update") {
|
|
9202
|
-
file.mode = file.expectedProjectMode ?? await readModeOptional(
|
|
9403
|
+
file.mode = file.expectedProjectMode ?? await readModeOptional(join23(artifactSet.targetDirectory, file.path));
|
|
9203
9404
|
}
|
|
9204
9405
|
if (file.action === "create") {
|
|
9205
|
-
file.parentMode = await readSourceParentMode(
|
|
9406
|
+
file.parentMode = await readSourceParentMode(join23(artifactSet.targetDirectory, dirname6(file.path)));
|
|
9206
9407
|
}
|
|
9207
9408
|
}
|
|
9208
9409
|
const plannedPaths = new Set(files.map(({ path: path2 }) => path2));
|
|
@@ -9308,7 +9509,7 @@ function assertArtifactIdentity(identity, expectedVersion) {
|
|
|
9308
9509
|
}
|
|
9309
9510
|
|
|
9310
9511
|
// src/reconcile/proof-profile.ts
|
|
9311
|
-
import { join as
|
|
9512
|
+
import { join as join24 } from "path";
|
|
9312
9513
|
|
|
9313
9514
|
// src/reconcile/reconcile-utils.ts
|
|
9314
9515
|
import { readFile as readFile18 } from "fs/promises";
|
|
@@ -9352,8 +9553,8 @@ async function matchesAnchors(profile, input) {
|
|
|
9352
9553
|
return false;
|
|
9353
9554
|
}
|
|
9354
9555
|
const [current, source] = await Promise.all([
|
|
9355
|
-
readOptionalFile(
|
|
9356
|
-
readOptionalFile(
|
|
9556
|
+
readOptionalFile(join24(input.projectDirectory, anchor.path)),
|
|
9557
|
+
readOptionalFile(join24(input.sourceDirectory, anchor.path))
|
|
9357
9558
|
]);
|
|
9358
9559
|
if (!current || !source || hashContent(current) !== anchor.currentHash || hashContent(source) !== anchor.sourceArtifactHash) {
|
|
9359
9560
|
return false;
|
|
@@ -9382,7 +9583,7 @@ async function resolveReconcileProofContext(input) {
|
|
|
9382
9583
|
}
|
|
9383
9584
|
|
|
9384
9585
|
// src/reconcile/reconcile-entry.ts
|
|
9385
|
-
import { join as
|
|
9586
|
+
import { join as join25 } from "path";
|
|
9386
9587
|
async function reconcileManagedEntry(input) {
|
|
9387
9588
|
if (!input.currentHash)
|
|
9388
9589
|
return { reason: "\u5F53\u524D\u6587\u4EF6\u7F3A\u5931" };
|
|
@@ -9396,11 +9597,11 @@ async function reconcileManagedEntry(input) {
|
|
|
9396
9597
|
if (!proof2 || !input.sourceEntry) {
|
|
9397
9598
|
return { reason: "\u6CA1\u6709\u5339\u914D\u7684\u4E0D\u53EF\u53D8 proof" };
|
|
9398
9599
|
}
|
|
9399
|
-
const source = await readOptionalFile(
|
|
9600
|
+
const source = await readOptionalFile(join25(input.sourceDirectory, input.entry.path));
|
|
9400
9601
|
if (!source || hashContent(source) !== proof2.artifactHash || artifactHashOf(input.sourceEntry) !== proof2.artifactHash) {
|
|
9401
9602
|
return { reason: "\u516C\u5F00\u6765\u6E90 artifact \u4E0E proof \u4E0D\u4E00\u81F4" };
|
|
9402
9603
|
}
|
|
9403
|
-
const target = await readOptionalFile(
|
|
9604
|
+
const target = await readOptionalFile(join25(input.targetDirectory, input.entry.path));
|
|
9404
9605
|
const targetHash = target ? hashContent(target) : undefined;
|
|
9405
9606
|
if (input.targetEntry && targetHash !== artifactHashOf(input.targetEntry)) {
|
|
9406
9607
|
return { reason: "\u516C\u5F00\u76EE\u6807 artifact \u4E0E\u6E05\u5355\u4E0D\u4E00\u81F4" };
|
|
@@ -9549,7 +9750,7 @@ class DefaultManagedReconciler {
|
|
|
9549
9750
|
const nextEntries = [];
|
|
9550
9751
|
const id = createReconcileId(sourceVersion, input.targetVersion);
|
|
9551
9752
|
const reportPath = `.windy/reports/${id}.md`;
|
|
9552
|
-
const reportContent = await readOptionalFile(
|
|
9753
|
+
const reportContent = await readOptionalFile(join26(input.projectDirectory, reportPath));
|
|
9553
9754
|
const inspectEveryDrift = sourceVersion === input.targetVersion;
|
|
9554
9755
|
const provenPaths = provenPathsForIdentity(effectiveProofs, {
|
|
9555
9756
|
sourceVersion,
|
|
@@ -9577,7 +9778,7 @@ class DefaultManagedReconciler {
|
|
|
9577
9778
|
nextEntries.push(entry);
|
|
9578
9779
|
continue;
|
|
9579
9780
|
}
|
|
9580
|
-
const current = await readOptionalFile(
|
|
9781
|
+
const current = await readOptionalFile(join26(input.projectDirectory, entry.path));
|
|
9581
9782
|
const currentHash = current ? hashContent(current) : undefined;
|
|
9582
9783
|
if (!guardedPaths.has(entry.path)) {
|
|
9583
9784
|
guards.push({ path: entry.path, hash: currentHash });
|
|
@@ -9622,7 +9823,7 @@ class DefaultManagedReconciler {
|
|
|
9622
9823
|
unresolved,
|
|
9623
9824
|
nextManifest: {
|
|
9624
9825
|
...manifest,
|
|
9625
|
-
schemaVersion: manifest.schemaVersion === 3 ? 3 : 2,
|
|
9826
|
+
schemaVersion: manifest.schemaVersion === 4 ? 4 : manifest.schemaVersion === 3 ? 3 : 2,
|
|
9626
9827
|
files: nextEntries
|
|
9627
9828
|
},
|
|
9628
9829
|
guards,
|
|
@@ -9683,7 +9884,7 @@ class DefaultManagedReconciler {
|
|
|
9683
9884
|
import { createHash as createHash6 } from "crypto";
|
|
9684
9885
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, readFile as readFile19, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
9685
9886
|
import { tmpdir as tmpdir2 } from "os";
|
|
9686
|
-
import { basename as basename7, join as
|
|
9887
|
+
import { basename as basename7, join as join27 } from "path";
|
|
9687
9888
|
|
|
9688
9889
|
// src/reconcile/safe-archive.ts
|
|
9689
9890
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
@@ -9731,7 +9932,7 @@ class PublicReconcileArtifactProvider {
|
|
|
9731
9932
|
this.#fetch = fetcher;
|
|
9732
9933
|
}
|
|
9733
9934
|
async prepare(input) {
|
|
9734
|
-
const temporaryRoot = await mkdtemp2(
|
|
9935
|
+
const temporaryRoot = await mkdtemp2(join27(tmpdir2(), "windy-reconcile-"));
|
|
9735
9936
|
try {
|
|
9736
9937
|
const provenance = await readGenerationProvenance(input.projectDirectory);
|
|
9737
9938
|
const sourceArtifact = await downloadPublicArtifact(temporaryRoot, "source", input.sourceVersion, input.sourceIntegrity, this.#fetch);
|
|
@@ -9766,17 +9967,17 @@ async function downloadPublicArtifact(temporaryRoot, label, version, expectedInt
|
|
|
9766
9967
|
}
|
|
9767
9968
|
const archive = await readBoundedResponse(response, `create-windy@${version} artifact `);
|
|
9768
9969
|
verifyArchive(metadata, archive);
|
|
9769
|
-
const destination =
|
|
9770
|
-
const archivePath =
|
|
9970
|
+
const destination = join27(temporaryRoot, `${label}-artifact`);
|
|
9971
|
+
const archivePath = join27(temporaryRoot, `${label}.tgz`);
|
|
9771
9972
|
await writeFile10(archivePath, archive);
|
|
9772
9973
|
await extractSafeTarArchive({
|
|
9773
9974
|
archivePath,
|
|
9774
9975
|
destination,
|
|
9775
9976
|
cwd: temporaryRoot
|
|
9776
9977
|
});
|
|
9777
|
-
const packageRoot2 =
|
|
9778
|
-
const packageJson = JSON.parse(await readFile19(
|
|
9779
|
-
const template = await readTemplateMetadata(
|
|
9978
|
+
const packageRoot2 = join27(destination, "package");
|
|
9979
|
+
const packageJson = JSON.parse(await readFile19(join27(packageRoot2, "package.json"), "utf8"));
|
|
9980
|
+
const template = await readTemplateMetadata(join27(packageRoot2, "template"));
|
|
9780
9981
|
if (packageJson.name !== "create-windy" || packageJson.version !== version || template.generatorVersion !== version) {
|
|
9781
9982
|
throw new Error(`create-windy@${version} \u5305\u8EAB\u4EFD\u6216\u6A21\u677F\u5143\u6570\u636E\u4E0D\u4E00\u81F4`);
|
|
9782
9983
|
}
|
|
@@ -9908,10 +10109,10 @@ function verifyArchive(metadata, archive) {
|
|
|
9908
10109
|
}
|
|
9909
10110
|
}
|
|
9910
10111
|
async function generatePublicArtifactProject(temporaryRoot, label, projectName, packageRoot2, provenance) {
|
|
9911
|
-
const parent =
|
|
10112
|
+
const parent = join27(temporaryRoot, label);
|
|
9912
10113
|
await mkdir6(parent, { recursive: true });
|
|
9913
10114
|
const args = [
|
|
9914
|
-
|
|
10115
|
+
join27(packageRoot2, "dist/cli.js"),
|
|
9915
10116
|
projectName,
|
|
9916
10117
|
"--yes",
|
|
9917
10118
|
"--skip-install",
|
|
@@ -9931,17 +10132,17 @@ async function generatePublicArtifactProject(temporaryRoot, label, projectName,
|
|
|
9931
10132
|
}
|
|
9932
10133
|
}
|
|
9933
10134
|
await assertProcess("bun", args, parent);
|
|
9934
|
-
return
|
|
10135
|
+
return join27(parent, projectName);
|
|
9935
10136
|
}
|
|
9936
10137
|
async function readArtifactProjectName(projectDirectory) {
|
|
9937
|
-
const value = JSON.parse(await readFile19(
|
|
10138
|
+
const value = JSON.parse(await readFile19(join27(projectDirectory, "package.json"), "utf8"));
|
|
9938
10139
|
const candidate = value.name ?? basename7(projectDirectory);
|
|
9939
10140
|
return /^[a-z0-9][a-z0-9._-]*$/.test(candidate) && candidate !== "." && candidate !== ".." ? candidate : basename7(projectDirectory);
|
|
9940
10141
|
}
|
|
9941
10142
|
|
|
9942
10143
|
// src/update/doctor.ts
|
|
9943
10144
|
import { readFile as readFile20 } from "fs/promises";
|
|
9944
|
-
import { join as
|
|
10145
|
+
import { join as join28 } from "path";
|
|
9945
10146
|
var oxcPaths = new Set([
|
|
9946
10147
|
".oxfmtrc.json",
|
|
9947
10148
|
".oxlintrc.json",
|
|
@@ -9968,7 +10169,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
9968
10169
|
if (file.ownership === "project-owned")
|
|
9969
10170
|
continue;
|
|
9970
10171
|
try {
|
|
9971
|
-
const changed = hashContent(await readFile20(
|
|
10172
|
+
const changed = hashContent(await readFile20(join28(projectDirectory, file.path))) !== file.hash;
|
|
9972
10173
|
if (!changed)
|
|
9973
10174
|
continue;
|
|
9974
10175
|
modifiedManagedFiles.push(file.path);
|
|
@@ -9998,12 +10199,12 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
9998
10199
|
|
|
9999
10200
|
// src/update/updater.ts
|
|
10000
10201
|
import { readFile as readFile24, rm as rm9 } from "fs/promises";
|
|
10001
|
-
import { join as
|
|
10202
|
+
import { join as join32 } from "path";
|
|
10002
10203
|
|
|
10003
10204
|
// src/update/artifacts.ts
|
|
10004
10205
|
import { mkdtemp as mkdtemp3, readFile as readFile22 } from "fs/promises";
|
|
10005
10206
|
import { tmpdir as tmpdir3 } from "os";
|
|
10006
|
-
import { basename as basename8, join as
|
|
10207
|
+
import { basename as basename8, join as join30 } from "path";
|
|
10007
10208
|
|
|
10008
10209
|
// src/module-history.ts
|
|
10009
10210
|
import {
|
|
@@ -10013,15 +10214,15 @@ import {
|
|
|
10013
10214
|
readFile as readFile21,
|
|
10014
10215
|
writeFile as writeFile11
|
|
10015
10216
|
} from "fs/promises";
|
|
10016
|
-
import { dirname as dirname7, join as
|
|
10217
|
+
import { dirname as dirname7, join as join29, relative as relative4, sep as sep4 } from "path";
|
|
10017
10218
|
async function archiveRemovedModuleMigrations(input) {
|
|
10018
10219
|
await assertExistingHistoryIntegrity(input.projectDirectory);
|
|
10019
|
-
let changed = await copyMigrationTree(input.projectDirectory, ".windy/module-history",
|
|
10220
|
+
let changed = await copyMigrationTree(input.projectDirectory, ".windy/module-history", join29(input.targetDirectory, ".windy/module-history"));
|
|
10020
10221
|
const target = new Set(input.targetModules);
|
|
10021
10222
|
const removed = moduleDistributionCatalog.filter(({ name }) => input.sourceModules.includes(name) && !target.has(name));
|
|
10022
10223
|
for (const manifest of removed) {
|
|
10023
10224
|
for (const root of manifest.migrationRoots) {
|
|
10024
|
-
changed = await copyMigrationTree(input.projectDirectory, root,
|
|
10225
|
+
changed = await copyMigrationTree(input.projectDirectory, root, join29(input.targetDirectory, ".windy/module-history", manifest.name, root)) || changed;
|
|
10025
10226
|
}
|
|
10026
10227
|
}
|
|
10027
10228
|
if (changed) {
|
|
@@ -10029,7 +10230,7 @@ async function archiveRemovedModuleMigrations(input) {
|
|
|
10029
10230
|
}
|
|
10030
10231
|
}
|
|
10031
10232
|
async function assertExistingHistoryIntegrity(projectDirectory) {
|
|
10032
|
-
const historyRoot =
|
|
10233
|
+
const historyRoot = join29(projectDirectory, ".windy/module-history");
|
|
10033
10234
|
const metadata = await readOptionalMetadata(historyRoot);
|
|
10034
10235
|
if (!metadata)
|
|
10035
10236
|
return;
|
|
@@ -10050,7 +10251,7 @@ async function assertExistingHistoryIntegrity(projectDirectory) {
|
|
|
10050
10251
|
}
|
|
10051
10252
|
}
|
|
10052
10253
|
async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
|
|
10053
|
-
const sourceRoot =
|
|
10254
|
+
const sourceRoot = join29(projectRoot, relativeRoot);
|
|
10054
10255
|
const rootMetadata = await readOptionalMetadata(sourceRoot);
|
|
10055
10256
|
if (!rootMetadata)
|
|
10056
10257
|
return false;
|
|
@@ -10058,7 +10259,7 @@ async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
|
|
|
10058
10259
|
throw new Error(`\u6A21\u5757 migration \u6839\u76EE\u5F55\u65E0\u6548\uFF1A${relativeRoot}`);
|
|
10059
10260
|
}
|
|
10060
10261
|
for (const source of await collectRegularFiles(sourceRoot)) {
|
|
10061
|
-
const target =
|
|
10262
|
+
const target = join29(targetRoot, relative4(sourceRoot, source).split(sep4).join("/"));
|
|
10062
10263
|
await mkdir7(dirname7(target), { recursive: true });
|
|
10063
10264
|
await writeFile11(target, await readFile21(source));
|
|
10064
10265
|
}
|
|
@@ -10070,7 +10271,7 @@ async function collectRegularFiles(root) {
|
|
|
10070
10271
|
return files.sort();
|
|
10071
10272
|
async function visit(directory) {
|
|
10072
10273
|
for (const name of (await readdir8(directory)).sort()) {
|
|
10073
|
-
const path2 =
|
|
10274
|
+
const path2 = join29(directory, name);
|
|
10074
10275
|
const metadata = await lstat5(path2);
|
|
10075
10276
|
if (metadata.isSymbolicLink()) {
|
|
10076
10277
|
throw new Error(`\u6A21\u5757 migration \u4E0D\u5141\u8BB8\u7B26\u53F7\u94FE\u63A5\uFF1A${path2}`);
|
|
@@ -10100,10 +10301,10 @@ class PublishedArtifactProvider {
|
|
|
10100
10301
|
this.#fetch = fetcher;
|
|
10101
10302
|
}
|
|
10102
10303
|
async prepare(projectDirectory, provenance, targetModules) {
|
|
10103
|
-
const temporaryRoot = await mkdtemp3(
|
|
10304
|
+
const temporaryRoot = await mkdtemp3(join30(tmpdir3(), "windy-update-"));
|
|
10104
10305
|
const projectName = await readProjectName3(projectDirectory);
|
|
10105
|
-
const baseDirectory =
|
|
10106
|
-
const targetDirectory =
|
|
10306
|
+
const baseDirectory = join30(temporaryRoot, "base", projectName);
|
|
10307
|
+
const targetDirectory = join30(temporaryRoot, "target", projectName);
|
|
10107
10308
|
const baseArtifact = await downloadPublicArtifact(temporaryRoot, "base", provenance.generatorVersion, undefined, this.#fetch);
|
|
10108
10309
|
if (baseArtifact.identity.templateCommit !== provenance.templateCommit) {
|
|
10109
10310
|
throw new Error("Project Lock \u4E0E\u6765\u6E90 registry artifact \u7684 template commit \u4E0D\u4E00\u81F4");
|
|
@@ -10131,7 +10332,7 @@ class PublishedArtifactProvider {
|
|
|
10131
10332
|
}
|
|
10132
10333
|
}
|
|
10133
10334
|
async function readProjectName3(projectDirectory) {
|
|
10134
|
-
const source = JSON.parse(await readFile22(
|
|
10335
|
+
const source = JSON.parse(await readFile22(join30(projectDirectory, "package.json"), "utf8"));
|
|
10135
10336
|
return source.name || basename8(projectDirectory);
|
|
10136
10337
|
}
|
|
10137
10338
|
|
|
@@ -10148,9 +10349,7 @@ function buildFinalManagedManifest(plan, generationContent, finalizedHashes = ne
|
|
|
10148
10349
|
const basis2 = target.get(path2) ?? source.get(path2);
|
|
10149
10350
|
if (!basis2)
|
|
10150
10351
|
throw new Error("\u6700\u7EC8\u53D7\u7BA1\u6E05\u5355\u7F3A\u5C11 Project Lock \u6761\u76EE");
|
|
10151
|
-
|
|
10152
|
-
clean2.hash = hashContent(generationContent);
|
|
10153
|
-
files.push(clean2);
|
|
10352
|
+
files.push(freezePresentEntry(basis2, hashContent(generationContent)));
|
|
10154
10353
|
continue;
|
|
10155
10354
|
}
|
|
10156
10355
|
const file = filePlans.get(path2);
|
|
@@ -10176,14 +10375,18 @@ function buildFinalManagedManifest(plan, generationContent, finalizedHashes = ne
|
|
|
10176
10375
|
});
|
|
10177
10376
|
continue;
|
|
10178
10377
|
}
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10378
|
+
files.push(freezePresentEntry(basis, frozenManifestHash(file, finalizedHashes)));
|
|
10379
|
+
}
|
|
10380
|
+
return { ...plan.targetManifest, schemaVersion: 4, files };
|
|
10381
|
+
}
|
|
10382
|
+
function freezePresentEntry(basis, currentHash) {
|
|
10383
|
+
const artifactHash = artifactHashOf(basis);
|
|
10384
|
+
const clean = withoutReconciliation(basis);
|
|
10385
|
+
if (isMissingManagedFile(clean)) {
|
|
10386
|
+
throw new Error(`\u6700\u7EC8\u53D7\u7BA1\u6E05\u5355\u4E0D\u80FD\u4E3A\u7F3A\u5931\u8DEF\u5F84\u5199\u5165 hash\uFF1A${basis.path}`);
|
|
10185
10387
|
}
|
|
10186
|
-
|
|
10388
|
+
clean.hash = currentHash;
|
|
10389
|
+
return clean.ownership !== "project-owned" && artifactHash !== currentHash ? { ...clean, artifactHash } : clean;
|
|
10187
10390
|
}
|
|
10188
10391
|
function frozenManifestHash(file, finalizedHashes) {
|
|
10189
10392
|
if (file.action === "create" || file.action === "update") {
|
|
@@ -10200,7 +10403,7 @@ function frozenManifestHash(file, finalizedHashes) {
|
|
|
10200
10403
|
// src/update/format-stabilizer.ts
|
|
10201
10404
|
import { mkdir as mkdir8, mkdtemp as mkdtemp4, readFile as readFile23, rm as rm8, writeFile as writeFile12 } from "fs/promises";
|
|
10202
10405
|
import { tmpdir as tmpdir4 } from "os";
|
|
10203
|
-
import { dirname as dirname8, extname as extname4, join as
|
|
10406
|
+
import { dirname as dirname8, extname as extname4, join as join31, resolve as resolve6, sep as sep5 } from "path";
|
|
10204
10407
|
var formattedExtensions = new Set([
|
|
10205
10408
|
".cjs",
|
|
10206
10409
|
".css",
|
|
@@ -10239,7 +10442,7 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
|
|
|
10239
10442
|
}
|
|
10240
10443
|
}
|
|
10241
10444
|
inputs.set(".windy/generation.json", new TextEncoder().encode(generationContent));
|
|
10242
|
-
const temporaryRoot = await mkdtemp4(
|
|
10445
|
+
const temporaryRoot = await mkdtemp4(join31(tmpdir4(), "windy-update-format-"));
|
|
10243
10446
|
try {
|
|
10244
10447
|
const stagedPaths = await stageInputs(temporaryRoot, inputs);
|
|
10245
10448
|
for (let offset = 0;offset < stagedPaths.length; offset += 80) {
|
|
@@ -10247,7 +10450,7 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
|
|
|
10247
10450
|
"x",
|
|
10248
10451
|
"oxfmt",
|
|
10249
10452
|
"--write",
|
|
10250
|
-
`--config=${
|
|
10453
|
+
`--config=${join31(result2.plan.projectDirectory, ".oxfmtrc.json")}`,
|
|
10251
10454
|
...stagedPaths.slice(offset, offset + 80)
|
|
10252
10455
|
], result2.plan.projectDirectory);
|
|
10253
10456
|
}
|
|
@@ -10349,10 +10552,10 @@ class DefaultStarterUpdater {
|
|
|
10349
10552
|
status: dockerSync.every(({ sync }) => sync.synced) ? "passed" : "skipped"
|
|
10350
10553
|
});
|
|
10351
10554
|
if (this.#skipInstall) {
|
|
10352
|
-
steps.push({ name: "bun install
|
|
10555
|
+
steps.push({ name: "bun install", status: "skipped" });
|
|
10353
10556
|
} else {
|
|
10354
|
-
await this.#execute("bun", ["install"
|
|
10355
|
-
steps.push({ name: "bun install
|
|
10557
|
+
await this.#execute("bun", ["install"], result2.plan.projectDirectory);
|
|
10558
|
+
steps.push({ name: "bun install", status: "passed" });
|
|
10356
10559
|
}
|
|
10357
10560
|
const stabilized = await stabilizeManagedContent(result2, this.#execute, async (path2, content) => {
|
|
10358
10561
|
await writeOwnedTransactionFile(result2.plan.projectDirectory, path2, content, expectedForPath(result2, path2));
|
|
@@ -10371,7 +10574,7 @@ class DefaultStarterUpdater {
|
|
|
10371
10574
|
const plannedPaths = new Set(result2.plan.files.map(({ path: path2 }) => path2));
|
|
10372
10575
|
replaceExpectations(result2, verificationSnapshot.filter(({ path: path2 }) => !metadataPaths2.has(path2) && !plannedPaths.has(path2)));
|
|
10373
10576
|
verificationExpectationsFrozen = true;
|
|
10374
|
-
const packageJson = JSON.parse(await readFile24(
|
|
10577
|
+
const packageJson = JSON.parse(await readFile24(join32(result2.plan.projectDirectory, "package.json"), "utf8"));
|
|
10375
10578
|
for (const name of ["lint", "typecheck", "test", "build"]) {
|
|
10376
10579
|
if (!packageJson.scripts?.[name]) {
|
|
10377
10580
|
steps.push({ name: `bun run ${name}`, status: "skipped" });
|
|
@@ -10982,7 +11185,7 @@ import {
|
|
|
10982
11185
|
rm as rm12,
|
|
10983
11186
|
writeFile as writeFile13
|
|
10984
11187
|
} from "fs/promises";
|
|
10985
|
-
import { dirname as dirname9, join as
|
|
11188
|
+
import { dirname as dirname9, join as join33, relative as relative6 } from "path";
|
|
10986
11189
|
|
|
10987
11190
|
// src/module-schema/config.ts
|
|
10988
11191
|
import { readFile as readFile25 } from "fs/promises";
|
|
@@ -11123,17 +11326,17 @@ async function handoffModuleSchemaLineages(options) {
|
|
|
11123
11326
|
async function stageModuleLineages(project, temporaryRoot, config, execute) {
|
|
11124
11327
|
const outputs = [];
|
|
11125
11328
|
for (const [index, module3] of config.modules.entries()) {
|
|
11126
|
-
const output =
|
|
11329
|
+
const output = join33(temporaryRoot, `module-${index}`);
|
|
11127
11330
|
await mkdir9(output, { recursive: true });
|
|
11128
|
-
const generatedConfig =
|
|
11331
|
+
const generatedConfig = join33(temporaryRoot, `module-${index}.config.ts`);
|
|
11129
11332
|
await writeDrizzleConfig(generatedConfig, project, module3.schema, relative6(temporaryRoot, output));
|
|
11130
11333
|
await execute(generatedConfig, temporaryRoot);
|
|
11131
11334
|
const sqlFiles = await listSqlFiles(output);
|
|
11132
11335
|
if (sqlFiles.length !== 1) {
|
|
11133
11336
|
throw new Error(`${module3.key} baseline \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration`);
|
|
11134
11337
|
}
|
|
11135
|
-
await writeFile13(
|
|
11136
|
-
await writeFile13(
|
|
11338
|
+
await writeFile13(join33(output, sqlFiles[0]), metadataOnlySql("module"));
|
|
11339
|
+
await writeFile13(join33(output, "lineage.json"), `${JSON.stringify({
|
|
11137
11340
|
schemaVersion: 1,
|
|
11138
11341
|
moduleKey: module3.key,
|
|
11139
11342
|
schema: module3.schema,
|
|
@@ -11147,17 +11350,17 @@ async function stageModuleLineages(project, temporaryRoot, config, execute) {
|
|
|
11147
11350
|
}
|
|
11148
11351
|
async function stagePlatformLineage(project, temporaryRoot, config, execute) {
|
|
11149
11352
|
const source = resolveProjectPath(project, config.platform.out);
|
|
11150
|
-
const output =
|
|
11353
|
+
const output = join33(temporaryRoot, "platform");
|
|
11151
11354
|
await cp(source, output, { recursive: true });
|
|
11152
11355
|
const before = new Set(await listSqlFiles(output));
|
|
11153
|
-
const generatedConfig =
|
|
11356
|
+
const generatedConfig = join33(temporaryRoot, "platform.config.ts");
|
|
11154
11357
|
await writeDrizzleConfig(generatedConfig, project, config.platform.schema, relative6(temporaryRoot, output));
|
|
11155
11358
|
await execute(generatedConfig, temporaryRoot);
|
|
11156
11359
|
const created = (await listSqlFiles(output)).filter((path2) => !before.has(path2));
|
|
11157
11360
|
if (created.length !== 1) {
|
|
11158
11361
|
throw new Error("\u5E73\u53F0 lineage handoff \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration");
|
|
11159
11362
|
}
|
|
11160
|
-
const sqlPath =
|
|
11363
|
+
const sqlPath = join33(output, created[0]);
|
|
11161
11364
|
const allowed = config.modules.flatMap(({ legacyPlatformTables }) => legacyPlatformTables);
|
|
11162
11365
|
validatePlatformHandoffSql(await readFile26(sqlPath, "utf8"), allowed);
|
|
11163
11366
|
await writeFile13(sqlPath, metadataOnlySql("platform"));
|
|
@@ -11165,7 +11368,7 @@ async function stagePlatformLineage(project, temporaryRoot, config, execute) {
|
|
|
11165
11368
|
}
|
|
11166
11369
|
async function installStagedLineages(project, temporaryRoot, config, stagedModules, stagedPlatform) {
|
|
11167
11370
|
const platform = resolveProjectPath(project, config.platform.out);
|
|
11168
|
-
const backup =
|
|
11371
|
+
const backup = join33(temporaryRoot, "platform-backup");
|
|
11169
11372
|
const installed = [];
|
|
11170
11373
|
await rename(platform, backup);
|
|
11171
11374
|
try {
|
|
@@ -11212,9 +11415,9 @@ async function validateSchemaSources(project, config) {
|
|
|
11212
11415
|
}
|
|
11213
11416
|
}
|
|
11214
11417
|
async function createTemporaryRoot(project) {
|
|
11215
|
-
const windy =
|
|
11418
|
+
const windy = join33(project, ".windy");
|
|
11216
11419
|
await mkdir9(windy, { recursive: true });
|
|
11217
|
-
return await mkdtemp5(
|
|
11420
|
+
return await mkdtemp5(join33(windy, "module-schema-handoff-"));
|
|
11218
11421
|
}
|
|
11219
11422
|
async function listSqlFiles(root) {
|
|
11220
11423
|
return (await readdir9(root)).filter((path2) => path2.endsWith(".sql")).sort();
|