create-windy 0.3.15 → 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/README.md +1 -1
- package/dist/cli.js +928 -724
- 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 +6 -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,
|
|
@@ -8869,127 +8562,634 @@ async function planFile(path2, baseRoot, targetRoot, source, target, ours, prove
|
|
|
8869
8562
|
if (!oursChanged && !targetChanged) {
|
|
8870
8563
|
return readyPlannedFile(path2, ownership, "preserve", "\u53CC\u65B9\u672A\u4FEE\u6539", expected);
|
|
8871
8564
|
}
|
|
8565
|
+
if (targetChanged && path2.endsWith("package.json") && base) {
|
|
8566
|
+
const merged = mergePackageJson(base, ours, theirs);
|
|
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);
|
|
8568
|
+
}
|
|
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
|
+
}
|
|
8872
8573
|
if (!oursChanged) {
|
|
8873
8574
|
return readyPlannedFile(path2, ownership, "update", "\u4EC5 Windy \u4FEE\u6539", expected, theirs);
|
|
8874
8575
|
}
|
|
8875
|
-
if (!targetChanged) {
|
|
8876
|
-
return readyPlannedFile(path2, ownership, "preserve", "\u5BA2\u6237\u5DF2\u4FEE\u6539", expected);
|
|
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);
|
|
8877
9012
|
}
|
|
8878
|
-
|
|
8879
|
-
|
|
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 };
|
|
8880
9020
|
}
|
|
8881
|
-
|
|
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
|
}
|
|
@@ -9092,7 +9292,9 @@ var recipes = [
|
|
|
9092
9292
|
{ id: "starter-0.3.11-to-0.3.12", from: "0.3.11", to: "0.3.12" },
|
|
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
|
-
{ id: "starter-0.3.14-to-0.3.15", from: "0.3.14", to: "0.3.15" }
|
|
9295
|
+
{ id: "starter-0.3.14-to-0.3.15", from: "0.3.14", to: "0.3.15" },
|
|
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" }
|
|
9096
9298
|
];
|
|
9097
9299
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
9098
9300
|
if (sourceVersion === targetVersion)
|
|
@@ -9129,7 +9331,7 @@ var oxcManagedPaths = new Set([
|
|
|
9129
9331
|
]);
|
|
9130
9332
|
async function createUpdatePlan(request, artifacts) {
|
|
9131
9333
|
await assertProjectReadyForUpdate(request.projectDirectory);
|
|
9132
|
-
const manifestPath =
|
|
9334
|
+
const manifestPath = join23(request.projectDirectory, ".windy/files.json");
|
|
9133
9335
|
const expectedManifestHash = hashContent(await readFile17(manifestPath));
|
|
9134
9336
|
const generationSnapshot = await readGenerationProvenanceSnapshot(request.projectDirectory);
|
|
9135
9337
|
const provenance = generationSnapshot.provenance;
|
|
@@ -9175,8 +9377,8 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
9175
9377
|
const paths = new Set([...sourceEntries.keys(), ...targetEntries.keys()]);
|
|
9176
9378
|
for (const path2 of paths) {
|
|
9177
9379
|
if (!projectSnapshots.has(path2)) {
|
|
9178
|
-
projectSnapshots.set(path2, await readOptional2(
|
|
9179
|
-
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)));
|
|
9180
9382
|
}
|
|
9181
9383
|
}
|
|
9182
9384
|
const files = await planFiles({
|
|
@@ -9190,18 +9392,18 @@ async function createUpdatePlan(request, artifacts) {
|
|
|
9190
9392
|
manageGeneratedProjectOwned: request.targetModules !== undefined,
|
|
9191
9393
|
forcePlatformBaseline: request.forcePlatformBaseline === true
|
|
9192
9394
|
});
|
|
9193
|
-
deriveDockerfilePlans(files, projectSnapshots, new Set([
|
|
9395
|
+
await deriveDockerfilePlans(files, projectSnapshots, new Set([
|
|
9194
9396
|
...paths,
|
|
9195
9397
|
...workspaceSnapshot.manifests.keys(),
|
|
9196
9398
|
...targetWorkspaceSnapshot.manifests.keys()
|
|
9197
|
-
]), targetWorkspaceSnapshot.manifests);
|
|
9399
|
+
]), targetWorkspaceSnapshot.manifests, artifactSet.baseDirectory, artifactSet.targetDirectory);
|
|
9198
9400
|
for (const file of files) {
|
|
9199
9401
|
file.expectedProjectMode = projectModes.get(file.path);
|
|
9200
9402
|
if (file.action === "create" || file.action === "update") {
|
|
9201
|
-
file.mode = file.expectedProjectMode ?? await readModeOptional(
|
|
9403
|
+
file.mode = file.expectedProjectMode ?? await readModeOptional(join23(artifactSet.targetDirectory, file.path));
|
|
9202
9404
|
}
|
|
9203
9405
|
if (file.action === "create") {
|
|
9204
|
-
file.parentMode = await readSourceParentMode(
|
|
9406
|
+
file.parentMode = await readSourceParentMode(join23(artifactSet.targetDirectory, dirname6(file.path)));
|
|
9205
9407
|
}
|
|
9206
9408
|
}
|
|
9207
9409
|
const plannedPaths = new Set(files.map(({ path: path2 }) => path2));
|
|
@@ -9307,7 +9509,7 @@ function assertArtifactIdentity(identity, expectedVersion) {
|
|
|
9307
9509
|
}
|
|
9308
9510
|
|
|
9309
9511
|
// src/reconcile/proof-profile.ts
|
|
9310
|
-
import { join as
|
|
9512
|
+
import { join as join24 } from "path";
|
|
9311
9513
|
|
|
9312
9514
|
// src/reconcile/reconcile-utils.ts
|
|
9313
9515
|
import { readFile as readFile18 } from "fs/promises";
|
|
@@ -9351,8 +9553,8 @@ async function matchesAnchors(profile, input) {
|
|
|
9351
9553
|
return false;
|
|
9352
9554
|
}
|
|
9353
9555
|
const [current, source] = await Promise.all([
|
|
9354
|
-
readOptionalFile(
|
|
9355
|
-
readOptionalFile(
|
|
9556
|
+
readOptionalFile(join24(input.projectDirectory, anchor.path)),
|
|
9557
|
+
readOptionalFile(join24(input.sourceDirectory, anchor.path))
|
|
9356
9558
|
]);
|
|
9357
9559
|
if (!current || !source || hashContent(current) !== anchor.currentHash || hashContent(source) !== anchor.sourceArtifactHash) {
|
|
9358
9560
|
return false;
|
|
@@ -9381,7 +9583,7 @@ async function resolveReconcileProofContext(input) {
|
|
|
9381
9583
|
}
|
|
9382
9584
|
|
|
9383
9585
|
// src/reconcile/reconcile-entry.ts
|
|
9384
|
-
import { join as
|
|
9586
|
+
import { join as join25 } from "path";
|
|
9385
9587
|
async function reconcileManagedEntry(input) {
|
|
9386
9588
|
if (!input.currentHash)
|
|
9387
9589
|
return { reason: "\u5F53\u524D\u6587\u4EF6\u7F3A\u5931" };
|
|
@@ -9395,11 +9597,11 @@ async function reconcileManagedEntry(input) {
|
|
|
9395
9597
|
if (!proof2 || !input.sourceEntry) {
|
|
9396
9598
|
return { reason: "\u6CA1\u6709\u5339\u914D\u7684\u4E0D\u53EF\u53D8 proof" };
|
|
9397
9599
|
}
|
|
9398
|
-
const source = await readOptionalFile(
|
|
9600
|
+
const source = await readOptionalFile(join25(input.sourceDirectory, input.entry.path));
|
|
9399
9601
|
if (!source || hashContent(source) !== proof2.artifactHash || artifactHashOf(input.sourceEntry) !== proof2.artifactHash) {
|
|
9400
9602
|
return { reason: "\u516C\u5F00\u6765\u6E90 artifact \u4E0E proof \u4E0D\u4E00\u81F4" };
|
|
9401
9603
|
}
|
|
9402
|
-
const target = await readOptionalFile(
|
|
9604
|
+
const target = await readOptionalFile(join25(input.targetDirectory, input.entry.path));
|
|
9403
9605
|
const targetHash = target ? hashContent(target) : undefined;
|
|
9404
9606
|
if (input.targetEntry && targetHash !== artifactHashOf(input.targetEntry)) {
|
|
9405
9607
|
return { reason: "\u516C\u5F00\u76EE\u6807 artifact \u4E0E\u6E05\u5355\u4E0D\u4E00\u81F4" };
|
|
@@ -9548,7 +9750,7 @@ class DefaultManagedReconciler {
|
|
|
9548
9750
|
const nextEntries = [];
|
|
9549
9751
|
const id = createReconcileId(sourceVersion, input.targetVersion);
|
|
9550
9752
|
const reportPath = `.windy/reports/${id}.md`;
|
|
9551
|
-
const reportContent = await readOptionalFile(
|
|
9753
|
+
const reportContent = await readOptionalFile(join26(input.projectDirectory, reportPath));
|
|
9552
9754
|
const inspectEveryDrift = sourceVersion === input.targetVersion;
|
|
9553
9755
|
const provenPaths = provenPathsForIdentity(effectiveProofs, {
|
|
9554
9756
|
sourceVersion,
|
|
@@ -9576,7 +9778,7 @@ class DefaultManagedReconciler {
|
|
|
9576
9778
|
nextEntries.push(entry);
|
|
9577
9779
|
continue;
|
|
9578
9780
|
}
|
|
9579
|
-
const current = await readOptionalFile(
|
|
9781
|
+
const current = await readOptionalFile(join26(input.projectDirectory, entry.path));
|
|
9580
9782
|
const currentHash = current ? hashContent(current) : undefined;
|
|
9581
9783
|
if (!guardedPaths.has(entry.path)) {
|
|
9582
9784
|
guards.push({ path: entry.path, hash: currentHash });
|
|
@@ -9621,7 +9823,7 @@ class DefaultManagedReconciler {
|
|
|
9621
9823
|
unresolved,
|
|
9622
9824
|
nextManifest: {
|
|
9623
9825
|
...manifest,
|
|
9624
|
-
schemaVersion: manifest.schemaVersion === 3 ? 3 : 2,
|
|
9826
|
+
schemaVersion: manifest.schemaVersion === 4 ? 4 : manifest.schemaVersion === 3 ? 3 : 2,
|
|
9625
9827
|
files: nextEntries
|
|
9626
9828
|
},
|
|
9627
9829
|
guards,
|
|
@@ -9682,7 +9884,7 @@ class DefaultManagedReconciler {
|
|
|
9682
9884
|
import { createHash as createHash6 } from "crypto";
|
|
9683
9885
|
import { mkdir as mkdir6, mkdtemp as mkdtemp2, readFile as readFile19, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
9684
9886
|
import { tmpdir as tmpdir2 } from "os";
|
|
9685
|
-
import { basename as basename7, join as
|
|
9887
|
+
import { basename as basename7, join as join27 } from "path";
|
|
9686
9888
|
|
|
9687
9889
|
// src/reconcile/safe-archive.ts
|
|
9688
9890
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
@@ -9730,7 +9932,7 @@ class PublicReconcileArtifactProvider {
|
|
|
9730
9932
|
this.#fetch = fetcher;
|
|
9731
9933
|
}
|
|
9732
9934
|
async prepare(input) {
|
|
9733
|
-
const temporaryRoot = await mkdtemp2(
|
|
9935
|
+
const temporaryRoot = await mkdtemp2(join27(tmpdir2(), "windy-reconcile-"));
|
|
9734
9936
|
try {
|
|
9735
9937
|
const provenance = await readGenerationProvenance(input.projectDirectory);
|
|
9736
9938
|
const sourceArtifact = await downloadPublicArtifact(temporaryRoot, "source", input.sourceVersion, input.sourceIntegrity, this.#fetch);
|
|
@@ -9765,17 +9967,17 @@ async function downloadPublicArtifact(temporaryRoot, label, version, expectedInt
|
|
|
9765
9967
|
}
|
|
9766
9968
|
const archive = await readBoundedResponse(response, `create-windy@${version} artifact `);
|
|
9767
9969
|
verifyArchive(metadata, archive);
|
|
9768
|
-
const destination =
|
|
9769
|
-
const archivePath =
|
|
9970
|
+
const destination = join27(temporaryRoot, `${label}-artifact`);
|
|
9971
|
+
const archivePath = join27(temporaryRoot, `${label}.tgz`);
|
|
9770
9972
|
await writeFile10(archivePath, archive);
|
|
9771
9973
|
await extractSafeTarArchive({
|
|
9772
9974
|
archivePath,
|
|
9773
9975
|
destination,
|
|
9774
9976
|
cwd: temporaryRoot
|
|
9775
9977
|
});
|
|
9776
|
-
const packageRoot2 =
|
|
9777
|
-
const packageJson = JSON.parse(await readFile19(
|
|
9778
|
-
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"));
|
|
9779
9981
|
if (packageJson.name !== "create-windy" || packageJson.version !== version || template.generatorVersion !== version) {
|
|
9780
9982
|
throw new Error(`create-windy@${version} \u5305\u8EAB\u4EFD\u6216\u6A21\u677F\u5143\u6570\u636E\u4E0D\u4E00\u81F4`);
|
|
9781
9983
|
}
|
|
@@ -9907,10 +10109,10 @@ function verifyArchive(metadata, archive) {
|
|
|
9907
10109
|
}
|
|
9908
10110
|
}
|
|
9909
10111
|
async function generatePublicArtifactProject(temporaryRoot, label, projectName, packageRoot2, provenance) {
|
|
9910
|
-
const parent =
|
|
10112
|
+
const parent = join27(temporaryRoot, label);
|
|
9911
10113
|
await mkdir6(parent, { recursive: true });
|
|
9912
10114
|
const args = [
|
|
9913
|
-
|
|
10115
|
+
join27(packageRoot2, "dist/cli.js"),
|
|
9914
10116
|
projectName,
|
|
9915
10117
|
"--yes",
|
|
9916
10118
|
"--skip-install",
|
|
@@ -9930,17 +10132,17 @@ async function generatePublicArtifactProject(temporaryRoot, label, projectName,
|
|
|
9930
10132
|
}
|
|
9931
10133
|
}
|
|
9932
10134
|
await assertProcess("bun", args, parent);
|
|
9933
|
-
return
|
|
10135
|
+
return join27(parent, projectName);
|
|
9934
10136
|
}
|
|
9935
10137
|
async function readArtifactProjectName(projectDirectory) {
|
|
9936
|
-
const value = JSON.parse(await readFile19(
|
|
10138
|
+
const value = JSON.parse(await readFile19(join27(projectDirectory, "package.json"), "utf8"));
|
|
9937
10139
|
const candidate = value.name ?? basename7(projectDirectory);
|
|
9938
10140
|
return /^[a-z0-9][a-z0-9._-]*$/.test(candidate) && candidate !== "." && candidate !== ".." ? candidate : basename7(projectDirectory);
|
|
9939
10141
|
}
|
|
9940
10142
|
|
|
9941
10143
|
// src/update/doctor.ts
|
|
9942
10144
|
import { readFile as readFile20 } from "fs/promises";
|
|
9943
|
-
import { join as
|
|
10145
|
+
import { join as join28 } from "path";
|
|
9944
10146
|
var oxcPaths = new Set([
|
|
9945
10147
|
".oxfmtrc.json",
|
|
9946
10148
|
".oxlintrc.json",
|
|
@@ -9967,7 +10169,7 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
9967
10169
|
if (file.ownership === "project-owned")
|
|
9968
10170
|
continue;
|
|
9969
10171
|
try {
|
|
9970
|
-
const changed = hashContent(await readFile20(
|
|
10172
|
+
const changed = hashContent(await readFile20(join28(projectDirectory, file.path))) !== file.hash;
|
|
9971
10173
|
if (!changed)
|
|
9972
10174
|
continue;
|
|
9973
10175
|
modifiedManagedFiles.push(file.path);
|
|
@@ -9997,12 +10199,12 @@ async function diagnoseProject(projectDirectory, repair = false) {
|
|
|
9997
10199
|
|
|
9998
10200
|
// src/update/updater.ts
|
|
9999
10201
|
import { readFile as readFile24, rm as rm9 } from "fs/promises";
|
|
10000
|
-
import { join as
|
|
10202
|
+
import { join as join32 } from "path";
|
|
10001
10203
|
|
|
10002
10204
|
// src/update/artifacts.ts
|
|
10003
10205
|
import { mkdtemp as mkdtemp3, readFile as readFile22 } from "fs/promises";
|
|
10004
10206
|
import { tmpdir as tmpdir3 } from "os";
|
|
10005
|
-
import { basename as basename8, join as
|
|
10207
|
+
import { basename as basename8, join as join30 } from "path";
|
|
10006
10208
|
|
|
10007
10209
|
// src/module-history.ts
|
|
10008
10210
|
import {
|
|
@@ -10012,15 +10214,15 @@ import {
|
|
|
10012
10214
|
readFile as readFile21,
|
|
10013
10215
|
writeFile as writeFile11
|
|
10014
10216
|
} from "fs/promises";
|
|
10015
|
-
import { dirname as dirname7, join as
|
|
10217
|
+
import { dirname as dirname7, join as join29, relative as relative4, sep as sep4 } from "path";
|
|
10016
10218
|
async function archiveRemovedModuleMigrations(input) {
|
|
10017
10219
|
await assertExistingHistoryIntegrity(input.projectDirectory);
|
|
10018
|
-
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"));
|
|
10019
10221
|
const target = new Set(input.targetModules);
|
|
10020
10222
|
const removed = moduleDistributionCatalog.filter(({ name }) => input.sourceModules.includes(name) && !target.has(name));
|
|
10021
10223
|
for (const manifest of removed) {
|
|
10022
10224
|
for (const root of manifest.migrationRoots) {
|
|
10023
|
-
changed = await copyMigrationTree(input.projectDirectory, root,
|
|
10225
|
+
changed = await copyMigrationTree(input.projectDirectory, root, join29(input.targetDirectory, ".windy/module-history", manifest.name, root)) || changed;
|
|
10024
10226
|
}
|
|
10025
10227
|
}
|
|
10026
10228
|
if (changed) {
|
|
@@ -10028,7 +10230,7 @@ async function archiveRemovedModuleMigrations(input) {
|
|
|
10028
10230
|
}
|
|
10029
10231
|
}
|
|
10030
10232
|
async function assertExistingHistoryIntegrity(projectDirectory) {
|
|
10031
|
-
const historyRoot =
|
|
10233
|
+
const historyRoot = join29(projectDirectory, ".windy/module-history");
|
|
10032
10234
|
const metadata = await readOptionalMetadata(historyRoot);
|
|
10033
10235
|
if (!metadata)
|
|
10034
10236
|
return;
|
|
@@ -10049,7 +10251,7 @@ async function assertExistingHistoryIntegrity(projectDirectory) {
|
|
|
10049
10251
|
}
|
|
10050
10252
|
}
|
|
10051
10253
|
async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
|
|
10052
|
-
const sourceRoot =
|
|
10254
|
+
const sourceRoot = join29(projectRoot, relativeRoot);
|
|
10053
10255
|
const rootMetadata = await readOptionalMetadata(sourceRoot);
|
|
10054
10256
|
if (!rootMetadata)
|
|
10055
10257
|
return false;
|
|
@@ -10057,7 +10259,7 @@ async function copyMigrationTree(projectRoot, relativeRoot, targetRoot) {
|
|
|
10057
10259
|
throw new Error(`\u6A21\u5757 migration \u6839\u76EE\u5F55\u65E0\u6548\uFF1A${relativeRoot}`);
|
|
10058
10260
|
}
|
|
10059
10261
|
for (const source of await collectRegularFiles(sourceRoot)) {
|
|
10060
|
-
const target =
|
|
10262
|
+
const target = join29(targetRoot, relative4(sourceRoot, source).split(sep4).join("/"));
|
|
10061
10263
|
await mkdir7(dirname7(target), { recursive: true });
|
|
10062
10264
|
await writeFile11(target, await readFile21(source));
|
|
10063
10265
|
}
|
|
@@ -10069,7 +10271,7 @@ async function collectRegularFiles(root) {
|
|
|
10069
10271
|
return files.sort();
|
|
10070
10272
|
async function visit(directory) {
|
|
10071
10273
|
for (const name of (await readdir8(directory)).sort()) {
|
|
10072
|
-
const path2 =
|
|
10274
|
+
const path2 = join29(directory, name);
|
|
10073
10275
|
const metadata = await lstat5(path2);
|
|
10074
10276
|
if (metadata.isSymbolicLink()) {
|
|
10075
10277
|
throw new Error(`\u6A21\u5757 migration \u4E0D\u5141\u8BB8\u7B26\u53F7\u94FE\u63A5\uFF1A${path2}`);
|
|
@@ -10099,10 +10301,10 @@ class PublishedArtifactProvider {
|
|
|
10099
10301
|
this.#fetch = fetcher;
|
|
10100
10302
|
}
|
|
10101
10303
|
async prepare(projectDirectory, provenance, targetModules) {
|
|
10102
|
-
const temporaryRoot = await mkdtemp3(
|
|
10304
|
+
const temporaryRoot = await mkdtemp3(join30(tmpdir3(), "windy-update-"));
|
|
10103
10305
|
const projectName = await readProjectName3(projectDirectory);
|
|
10104
|
-
const baseDirectory =
|
|
10105
|
-
const targetDirectory =
|
|
10306
|
+
const baseDirectory = join30(temporaryRoot, "base", projectName);
|
|
10307
|
+
const targetDirectory = join30(temporaryRoot, "target", projectName);
|
|
10106
10308
|
const baseArtifact = await downloadPublicArtifact(temporaryRoot, "base", provenance.generatorVersion, undefined, this.#fetch);
|
|
10107
10309
|
if (baseArtifact.identity.templateCommit !== provenance.templateCommit) {
|
|
10108
10310
|
throw new Error("Project Lock \u4E0E\u6765\u6E90 registry artifact \u7684 template commit \u4E0D\u4E00\u81F4");
|
|
@@ -10130,7 +10332,7 @@ class PublishedArtifactProvider {
|
|
|
10130
10332
|
}
|
|
10131
10333
|
}
|
|
10132
10334
|
async function readProjectName3(projectDirectory) {
|
|
10133
|
-
const source = JSON.parse(await readFile22(
|
|
10335
|
+
const source = JSON.parse(await readFile22(join30(projectDirectory, "package.json"), "utf8"));
|
|
10134
10336
|
return source.name || basename8(projectDirectory);
|
|
10135
10337
|
}
|
|
10136
10338
|
|
|
@@ -10147,9 +10349,7 @@ function buildFinalManagedManifest(plan, generationContent, finalizedHashes = ne
|
|
|
10147
10349
|
const basis2 = target.get(path2) ?? source.get(path2);
|
|
10148
10350
|
if (!basis2)
|
|
10149
10351
|
throw new Error("\u6700\u7EC8\u53D7\u7BA1\u6E05\u5355\u7F3A\u5C11 Project Lock \u6761\u76EE");
|
|
10150
|
-
|
|
10151
|
-
clean2.hash = hashContent(generationContent);
|
|
10152
|
-
files.push(clean2);
|
|
10352
|
+
files.push(freezePresentEntry(basis2, hashContent(generationContent)));
|
|
10153
10353
|
continue;
|
|
10154
10354
|
}
|
|
10155
10355
|
const file = filePlans.get(path2);
|
|
@@ -10175,14 +10375,18 @@ function buildFinalManagedManifest(plan, generationContent, finalizedHashes = ne
|
|
|
10175
10375
|
});
|
|
10176
10376
|
continue;
|
|
10177
10377
|
}
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
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}`);
|
|
10184
10387
|
}
|
|
10185
|
-
|
|
10388
|
+
clean.hash = currentHash;
|
|
10389
|
+
return clean.ownership !== "project-owned" && artifactHash !== currentHash ? { ...clean, artifactHash } : clean;
|
|
10186
10390
|
}
|
|
10187
10391
|
function frozenManifestHash(file, finalizedHashes) {
|
|
10188
10392
|
if (file.action === "create" || file.action === "update") {
|
|
@@ -10199,7 +10403,7 @@ function frozenManifestHash(file, finalizedHashes) {
|
|
|
10199
10403
|
// src/update/format-stabilizer.ts
|
|
10200
10404
|
import { mkdir as mkdir8, mkdtemp as mkdtemp4, readFile as readFile23, rm as rm8, writeFile as writeFile12 } from "fs/promises";
|
|
10201
10405
|
import { tmpdir as tmpdir4 } from "os";
|
|
10202
|
-
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";
|
|
10203
10407
|
var formattedExtensions = new Set([
|
|
10204
10408
|
".cjs",
|
|
10205
10409
|
".css",
|
|
@@ -10238,7 +10442,7 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
|
|
|
10238
10442
|
}
|
|
10239
10443
|
}
|
|
10240
10444
|
inputs.set(".windy/generation.json", new TextEncoder().encode(generationContent));
|
|
10241
|
-
const temporaryRoot = await mkdtemp4(
|
|
10445
|
+
const temporaryRoot = await mkdtemp4(join31(tmpdir4(), "windy-update-format-"));
|
|
10242
10446
|
try {
|
|
10243
10447
|
const stagedPaths = await stageInputs(temporaryRoot, inputs);
|
|
10244
10448
|
for (let offset = 0;offset < stagedPaths.length; offset += 80) {
|
|
@@ -10246,7 +10450,7 @@ async function stabilizeManagedContent(result2, execute, writeProjectFile) {
|
|
|
10246
10450
|
"x",
|
|
10247
10451
|
"oxfmt",
|
|
10248
10452
|
"--write",
|
|
10249
|
-
`--config=${
|
|
10453
|
+
`--config=${join31(result2.plan.projectDirectory, ".oxfmtrc.json")}`,
|
|
10250
10454
|
...stagedPaths.slice(offset, offset + 80)
|
|
10251
10455
|
], result2.plan.projectDirectory);
|
|
10252
10456
|
}
|
|
@@ -10348,10 +10552,10 @@ class DefaultStarterUpdater {
|
|
|
10348
10552
|
status: dockerSync.every(({ sync }) => sync.synced) ? "passed" : "skipped"
|
|
10349
10553
|
});
|
|
10350
10554
|
if (this.#skipInstall) {
|
|
10351
|
-
steps.push({ name: "bun install
|
|
10555
|
+
steps.push({ name: "bun install", status: "skipped" });
|
|
10352
10556
|
} else {
|
|
10353
|
-
await this.#execute("bun", ["install"
|
|
10354
|
-
steps.push({ name: "bun install
|
|
10557
|
+
await this.#execute("bun", ["install"], result2.plan.projectDirectory);
|
|
10558
|
+
steps.push({ name: "bun install", status: "passed" });
|
|
10355
10559
|
}
|
|
10356
10560
|
const stabilized = await stabilizeManagedContent(result2, this.#execute, async (path2, content) => {
|
|
10357
10561
|
await writeOwnedTransactionFile(result2.plan.projectDirectory, path2, content, expectedForPath(result2, path2));
|
|
@@ -10370,7 +10574,7 @@ class DefaultStarterUpdater {
|
|
|
10370
10574
|
const plannedPaths = new Set(result2.plan.files.map(({ path: path2 }) => path2));
|
|
10371
10575
|
replaceExpectations(result2, verificationSnapshot.filter(({ path: path2 }) => !metadataPaths2.has(path2) && !plannedPaths.has(path2)));
|
|
10372
10576
|
verificationExpectationsFrozen = true;
|
|
10373
|
-
const packageJson = JSON.parse(await readFile24(
|
|
10577
|
+
const packageJson = JSON.parse(await readFile24(join32(result2.plan.projectDirectory, "package.json"), "utf8"));
|
|
10374
10578
|
for (const name of ["lint", "typecheck", "test", "build"]) {
|
|
10375
10579
|
if (!packageJson.scripts?.[name]) {
|
|
10376
10580
|
steps.push({ name: `bun run ${name}`, status: "skipped" });
|
|
@@ -10981,7 +11185,7 @@ import {
|
|
|
10981
11185
|
rm as rm12,
|
|
10982
11186
|
writeFile as writeFile13
|
|
10983
11187
|
} from "fs/promises";
|
|
10984
|
-
import { dirname as dirname9, join as
|
|
11188
|
+
import { dirname as dirname9, join as join33, relative as relative6 } from "path";
|
|
10985
11189
|
|
|
10986
11190
|
// src/module-schema/config.ts
|
|
10987
11191
|
import { readFile as readFile25 } from "fs/promises";
|
|
@@ -11122,17 +11326,17 @@ async function handoffModuleSchemaLineages(options) {
|
|
|
11122
11326
|
async function stageModuleLineages(project, temporaryRoot, config, execute) {
|
|
11123
11327
|
const outputs = [];
|
|
11124
11328
|
for (const [index, module3] of config.modules.entries()) {
|
|
11125
|
-
const output =
|
|
11329
|
+
const output = join33(temporaryRoot, `module-${index}`);
|
|
11126
11330
|
await mkdir9(output, { recursive: true });
|
|
11127
|
-
const generatedConfig =
|
|
11331
|
+
const generatedConfig = join33(temporaryRoot, `module-${index}.config.ts`);
|
|
11128
11332
|
await writeDrizzleConfig(generatedConfig, project, module3.schema, relative6(temporaryRoot, output));
|
|
11129
11333
|
await execute(generatedConfig, temporaryRoot);
|
|
11130
11334
|
const sqlFiles = await listSqlFiles(output);
|
|
11131
11335
|
if (sqlFiles.length !== 1) {
|
|
11132
11336
|
throw new Error(`${module3.key} baseline \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration`);
|
|
11133
11337
|
}
|
|
11134
|
-
await writeFile13(
|
|
11135
|
-
await writeFile13(
|
|
11338
|
+
await writeFile13(join33(output, sqlFiles[0]), metadataOnlySql("module"));
|
|
11339
|
+
await writeFile13(join33(output, "lineage.json"), `${JSON.stringify({
|
|
11136
11340
|
schemaVersion: 1,
|
|
11137
11341
|
moduleKey: module3.key,
|
|
11138
11342
|
schema: module3.schema,
|
|
@@ -11146,17 +11350,17 @@ async function stageModuleLineages(project, temporaryRoot, config, execute) {
|
|
|
11146
11350
|
}
|
|
11147
11351
|
async function stagePlatformLineage(project, temporaryRoot, config, execute) {
|
|
11148
11352
|
const source = resolveProjectPath(project, config.platform.out);
|
|
11149
|
-
const output =
|
|
11353
|
+
const output = join33(temporaryRoot, "platform");
|
|
11150
11354
|
await cp(source, output, { recursive: true });
|
|
11151
11355
|
const before = new Set(await listSqlFiles(output));
|
|
11152
|
-
const generatedConfig =
|
|
11356
|
+
const generatedConfig = join33(temporaryRoot, "platform.config.ts");
|
|
11153
11357
|
await writeDrizzleConfig(generatedConfig, project, config.platform.schema, relative6(temporaryRoot, output));
|
|
11154
11358
|
await execute(generatedConfig, temporaryRoot);
|
|
11155
11359
|
const created = (await listSqlFiles(output)).filter((path2) => !before.has(path2));
|
|
11156
11360
|
if (created.length !== 1) {
|
|
11157
11361
|
throw new Error("\u5E73\u53F0 lineage handoff \u5FC5\u987B\u6070\u597D\u751F\u6210\u4E00\u4E2A migration");
|
|
11158
11362
|
}
|
|
11159
|
-
const sqlPath =
|
|
11363
|
+
const sqlPath = join33(output, created[0]);
|
|
11160
11364
|
const allowed = config.modules.flatMap(({ legacyPlatformTables }) => legacyPlatformTables);
|
|
11161
11365
|
validatePlatformHandoffSql(await readFile26(sqlPath, "utf8"), allowed);
|
|
11162
11366
|
await writeFile13(sqlPath, metadataOnlySql("platform"));
|
|
@@ -11164,7 +11368,7 @@ async function stagePlatformLineage(project, temporaryRoot, config, execute) {
|
|
|
11164
11368
|
}
|
|
11165
11369
|
async function installStagedLineages(project, temporaryRoot, config, stagedModules, stagedPlatform) {
|
|
11166
11370
|
const platform = resolveProjectPath(project, config.platform.out);
|
|
11167
|
-
const backup =
|
|
11371
|
+
const backup = join33(temporaryRoot, "platform-backup");
|
|
11168
11372
|
const installed = [];
|
|
11169
11373
|
await rename(platform, backup);
|
|
11170
11374
|
try {
|
|
@@ -11211,9 +11415,9 @@ async function validateSchemaSources(project, config) {
|
|
|
11211
11415
|
}
|
|
11212
11416
|
}
|
|
11213
11417
|
async function createTemporaryRoot(project) {
|
|
11214
|
-
const windy =
|
|
11418
|
+
const windy = join33(project, ".windy");
|
|
11215
11419
|
await mkdir9(windy, { recursive: true });
|
|
11216
|
-
return await mkdtemp5(
|
|
11420
|
+
return await mkdtemp5(join33(windy, "module-schema-handoff-"));
|
|
11217
11421
|
}
|
|
11218
11422
|
async function listSqlFiles(root) {
|
|
11219
11423
|
return (await readdir9(root)).filter((path2) => path2.endsWith(".sql")).sort();
|