mirror of
https://github.com/Jeuners/ECC.git
synced 2026-09-09 15:02:30 +02:00
fix(install): dedupe copy-file operations sharing a destination (#2429)
OpenCode ships override command files under .opencode/commands/ that shadow the generic commands/*.md sources. The manifest install plan recorded both writes against the same destination, so `ecc doctor` reported perpetual drift for the 29 shadowed command files and `ecc repair` "fixed" that phantom drift by copying the generic source over the correct override, corrupting the installed command while never clearing the warning. Dedupe copy-file operations by destination in createManifestInstallPlan, keeping the last writer to match the sequential apply order. install, repair, and doctor all consume this one builder, so a fresh install is clean and a single repair rewrites drifted state green. Fixes #2414
This commit is contained in:
parent
52f7e82a61
commit
c8c83ef428
2 changed files with 84 additions and 1 deletions
|
|
@ -688,6 +688,32 @@ function materializeScaffoldOperation(sourceRoot, operation) {
|
|||
});
|
||||
}
|
||||
|
||||
function dedupeCopyFileOperations(operations) {
|
||||
// A `copy-file` operation fully overwrites its destination, so when several
|
||||
// of them target the same path (e.g. a generic `commands/<name>.md` shadowed
|
||||
// by an OpenCode `.opencode/commands/<name>.md` override) only the last one
|
||||
// actually determines the installed content. Recording the shadowed earlier
|
||||
// writes in install-state makes `doctor` report perpetual drift and drives
|
||||
// `repair` to clobber the override with the generic source (issue #2414).
|
||||
// Keep only the last `copy-file` per destination — matching the sequential
|
||||
// apply order in applyInstallPlan — and leave every other operation kind
|
||||
// (e.g. accumulating `merge-json` writes into a shared config) untouched and
|
||||
// in order.
|
||||
const lastCopyIndexByDestination = new Map();
|
||||
operations.forEach((operation, index) => {
|
||||
if (operation.kind === 'copy-file' && operation.destinationPath) {
|
||||
lastCopyIndexByDestination.set(operation.destinationPath, index);
|
||||
}
|
||||
});
|
||||
|
||||
return operations.filter((operation, index) => {
|
||||
if (operation.kind !== 'copy-file' || !operation.destinationPath) {
|
||||
return true;
|
||||
}
|
||||
return lastCopyIndexByDestination.get(operation.destinationPath) === index;
|
||||
});
|
||||
}
|
||||
|
||||
function createManifestInstallPlan(options = {}) {
|
||||
const sourceRoot = options.sourceRoot || getSourceRoot();
|
||||
const projectRoot = options.projectRoot || process.cwd();
|
||||
|
|
@ -716,7 +742,9 @@ function createManifestInstallPlan(options = {}) {
|
|||
target
|
||||
});
|
||||
const adapter = getInstallTargetAdapter(target);
|
||||
const operations = plan.operations.flatMap(operation => materializeScaffoldOperation(sourceRoot, operation));
|
||||
const operations = dedupeCopyFileOperations(
|
||||
plan.operations.flatMap(operation => materializeScaffoldOperation(sourceRoot, operation))
|
||||
);
|
||||
const source = {
|
||||
repoVersion: getPackageVersion(sourceRoot),
|
||||
repoCommit: getRepoCommit(sourceRoot),
|
||||
|
|
@ -776,6 +804,7 @@ module.exports = {
|
|||
createLegacyCompatInstallPlan,
|
||||
createManifestInstallPlan,
|
||||
createLegacyInstallPlan,
|
||||
dedupeCopyFileOperations,
|
||||
getSourceRoot,
|
||||
listAvailableLanguages,
|
||||
parseInstallArgs
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue