mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-20 06:51:44 +02:00
Merge PR #3256: reconcile obsolete managed installer files safely
Preserve main OpenCode lease and activation gates and native Claude namespace receipts. Detect same-size in-place edits using bigint modification and change times during hashing and before deletion; verify both races and actual public CLI reinstall lifecycles.
This commit is contained in:
@@ -5,6 +5,8 @@ const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
const { createHash } = require('crypto');
|
||||
const { readInstallState, writeInstallState } = require('../../scripts/lib/install-state');
|
||||
|
||||
const cli = path.resolve(__dirname, '../../scripts/ecc.js');
|
||||
let passed = 0;
|
||||
@@ -51,6 +53,37 @@ for (const target of ['claude', 'claude-project']) {
|
||||
const report = JSON.parse(run(['doctor', '--target', target, '--json']).stdout);
|
||||
assert.strictEqual(report.summary.errorCount, 0);
|
||||
assert.strictEqual(report.summary.warningCount, 0);
|
||||
// Seed obsolete managed records from an older install, then exercise the
|
||||
// public reinstall instead of calling reconciliation directly.
|
||||
const statePath = path.join(installRoot, 'ecc/install-state.json');
|
||||
const obsoleteContent = '# obsolete managed rule\n';
|
||||
const stalePaths = ['obsolete-unchanged.md', 'obsolete-edited.md']
|
||||
.map(name => path.join(installRoot, 'rules/ecc/common', name));
|
||||
const state = readInstallState(statePath);
|
||||
for (const file of stalePaths) {
|
||||
fs.writeFileSync(file, obsoleteContent);
|
||||
state.operations.push({
|
||||
kind: 'copy-file', moduleId: 'rules-core',
|
||||
sourceRelativePath: `rules/common/${path.basename(file)}`,
|
||||
destinationPath: file, strategy: 'preserve-relative-path',
|
||||
ownership: 'managed', scaffoldOnly: false,
|
||||
contentTransform: 'claude-manual-plugin-namespace',
|
||||
contentSha256: createHash('sha256').update(obsoleteContent).digest('hex'),
|
||||
});
|
||||
}
|
||||
writeInstallState(statePath, state);
|
||||
const userEdit = '# user wants to keep this obsolete rule\n';
|
||||
fs.writeFileSync(stalePaths[1], userEdit);
|
||||
const reinstall = JSON.parse(run([...installArgs, '--json']).stdout).result;
|
||||
assert.ok(!fs.existsSync(stalePaths[0]), 'Unmodified obsolete managed files should be removed');
|
||||
assert.strictEqual(fs.readFileSync(stalePaths[1], 'utf8'), userEdit);
|
||||
assert.deepStrictEqual(reinstall.reconciledStalePaths, [stalePaths[0]]);
|
||||
assert.strictEqual(reinstall.warnings.filter(warning => warning.startsWith('Preserved orphaned file ')).length, 1);
|
||||
assert.ok(readInstallState(statePath).operations.every(operation => !stalePaths.includes(operation.destinationPath)));
|
||||
assertNativeNames();
|
||||
const afterReinstall = JSON.parse(run(['doctor', '--target', target, '--json']).stdout);
|
||||
assert.strictEqual(afterReinstall.summary.errorCount, 0);
|
||||
assert.strictEqual(afterReinstall.summary.warningCount, 0);
|
||||
fs.appendFileSync(rule, '\nlocal drift\n');
|
||||
run(['repair', '--target', target, '--json']);
|
||||
assertNativeNames();
|
||||
@@ -62,6 +95,7 @@ for (const target of ['claude', 'claude-project']) {
|
||||
run(['uninstall', '--target', target, '--json']);
|
||||
assert.ok(!fs.existsSync(rule));
|
||||
assert.strictEqual(fs.readFileSync(userFile, 'utf8'), 'keep this user file\n');
|
||||
assert.strictEqual(fs.readFileSync(stalePaths[1], 'utf8'), userEdit);
|
||||
passed++;
|
||||
console.log(` PASS ${target} install, doctor, repair, reinstall and uninstall preserve native names`);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user