mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-18 07:37:59 +02:00
feat(install): require an explicit hook decision at the apply layer
The guided installer asks how ECC hooks should run, but that consent lived only in the wizard path. Running install-apply directly with a profile that includes hooks-runtime still materialized the hook runtime with no disclosure and no decision. Gate the apply layer instead, so every entry point is covered: - disclose the six hook capability groups when a plan would materialize the hook runtime, and refuse to apply until the caller decides - --enable-hooks confirms the hook runtime; --no-hooks installs the rest of the selection without it and records the reduced module closure in install-state - surface the pending decision as a dry-run warning - show the same capability disclosure in the guided installer's plan preview, so the wizard's hook question states what it is asking about Plans that never materialize hooks (Kimi, --profile minimal, --without baseline:hooks) are unaffected and need no flag. Repair and uninstall operate on already-recorded state and stay unchanged. The capability taxonomy and the held-materialization behavior come from Samarjeet Singh Tomar's PR #2634, reworked to fit the single-decision consent model that shipped with the guided installer in #2649. Co-Authored-By: Samarjeet Singh Tomar <samar_tomar@hotmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Samarjeet Singh Tomar
Claude Fable 5
parent
a89cec9658
commit
6aaa41e028
@@ -169,6 +169,7 @@ function runTests() {
|
||||
excludeComponents: ['component:beta'],
|
||||
legacyLanguages: [],
|
||||
legacyMode: false,
|
||||
hookConsent: 'declined',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -179,6 +180,42 @@ function runTests() {
|
||||
'--modules', 'platform-configs',
|
||||
'--with', 'component:alpha',
|
||||
'--without', 'component:beta',
|
||||
'--no-hooks',
|
||||
]);
|
||||
})) passed += 1; else failed += 1;
|
||||
|
||||
if (test('buildInstallApplyArgs infers enabled hooks for older install-state records', () => {
|
||||
const record = {
|
||||
adapter: { target: 'cursor', kind: 'project' },
|
||||
state: {
|
||||
target: { target: 'cursor' },
|
||||
request: {
|
||||
profile: 'core',
|
||||
modules: [],
|
||||
includeComponents: [],
|
||||
excludeComponents: [],
|
||||
legacyLanguages: [],
|
||||
legacyMode: false,
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: ['rules-core', 'hooks-runtime'],
|
||||
skippedModules: [],
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
kind: 'copy-file',
|
||||
moduleId: 'hooks-runtime',
|
||||
sourceRelativePath: '.cursor/hooks.json',
|
||||
destinationPath: '/tmp/project/.cursor/hooks.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepStrictEqual(buildInstallApplyArgs(record), [
|
||||
'--target', 'cursor',
|
||||
'--profile', 'core',
|
||||
'--enable-hooks',
|
||||
]);
|
||||
})) passed += 1; else failed += 1;
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['typescript'], { cwd: projectDir, homeDir });
|
||||
const result = run(['typescript', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
@@ -173,7 +173,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['typescript'], { cwd: projectDir, homeDir });
|
||||
const result = run(['typescript', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
@@ -207,7 +207,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['--target', 'cursor', 'typescript'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--target', 'cursor', 'typescript', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-coding-style.mdc')));
|
||||
@@ -267,7 +267,7 @@ function runTests() {
|
||||
},
|
||||
}, null, 2));
|
||||
|
||||
const result = run(['--target', 'cursor', 'typescript'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--target', 'cursor', 'typescript', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const mcpConfig = readJson(path.join(projectDir, '.cursor', 'mcp.json'));
|
||||
@@ -525,7 +525,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
@@ -567,7 +567,7 @@ function runTests() {
|
||||
fs.writeFileSync(userRulePath, '# User custom rule\n');
|
||||
fs.writeFileSync(userSkillPath, '# User custom skill\n');
|
||||
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
assert.ok(result.stdout.includes('user-owned'), result.stdout);
|
||||
assert.ok(result.stdout.includes('Skipped operations:'), result.stdout);
|
||||
@@ -715,7 +715,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['--target', 'cursor', '--modules', 'platform-configs'], {
|
||||
const result = run(['--target', 'cursor', '--modules', 'platform-configs', '--enable-hooks'], {
|
||||
cwd: projectDir,
|
||||
homeDir,
|
||||
});
|
||||
@@ -752,7 +752,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
@@ -772,7 +772,7 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const claudeRoot = path.join(homeDir, '.claude');
|
||||
@@ -830,7 +830,7 @@ function runTests() {
|
||||
}, null, 2)
|
||||
);
|
||||
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const settings = readJson(path.join(claudeRoot, 'settings.json'));
|
||||
@@ -932,10 +932,10 @@ function runTests() {
|
||||
const projectDir = createTempDir('install-apply-project-');
|
||||
|
||||
try {
|
||||
const firstInstall = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const firstInstall = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(firstInstall.code, 0, firstInstall.stderr);
|
||||
|
||||
const secondInstall = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const secondInstall = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(secondInstall.code, 0, secondInstall.stderr);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
@@ -963,7 +963,7 @@ function runTests() {
|
||||
};
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(legacySettings, null, 2));
|
||||
|
||||
const secondInstall = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const secondInstall = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(secondInstall.code, 0, secondInstall.stderr);
|
||||
|
||||
const afterSecondInstall = readJson(settingsPath);
|
||||
@@ -991,7 +991,7 @@ function runTests() {
|
||||
};
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(customSettings, null, 2));
|
||||
|
||||
const install = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const install = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(install.code, 0, install.stderr);
|
||||
|
||||
const afterInstall = readJson(settingsPath);
|
||||
@@ -1018,7 +1018,7 @@ function runTests() {
|
||||
};
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(customSettings, null, 2));
|
||||
|
||||
const install = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const install = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(install.code, 0, install.stderr);
|
||||
|
||||
const afterInstall = readJson(settingsPath);
|
||||
@@ -1039,7 +1039,7 @@ function runTests() {
|
||||
const settingsPath = path.join(claudeRoot, 'settings.json');
|
||||
fs.writeFileSync(settingsPath, '{ invalid json\n');
|
||||
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
assert.strictEqual(fs.readFileSync(settingsPath, 'utf8'), '{ invalid json\n');
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'hooks', 'hooks.json')), 'hooks.json should still be copied');
|
||||
@@ -1060,7 +1060,7 @@ function runTests() {
|
||||
const settingsPath = path.join(claudeRoot, 'settings.json');
|
||||
fs.writeFileSync(settingsPath, '[]\n');
|
||||
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
const result = run(['--profile', 'core', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
assert.strictEqual(fs.readFileSync(settingsPath, 'utf8'), '[]\n');
|
||||
assert.ok(fs.existsSync(path.join(claudeRoot, 'hooks', 'hooks.json')), 'hooks.json should still be copied');
|
||||
@@ -1084,6 +1084,7 @@ function runTests() {
|
||||
applyInstallPlan({
|
||||
targetRoot,
|
||||
installStatePath,
|
||||
hookConsent: 'enabled',
|
||||
statePreview: {
|
||||
schemaVersion: 'ecc.install.v1',
|
||||
installedAt: new Date().toISOString(),
|
||||
@@ -1147,7 +1148,7 @@ function runTests() {
|
||||
exclude: ['capability:orchestration'],
|
||||
}, null, 2));
|
||||
|
||||
const result = run(['--config', configPath], { cwd: projectDir, homeDir });
|
||||
const result = run(['--config', configPath, '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'security-review', 'SKILL.md')));
|
||||
@@ -1179,7 +1180,7 @@ function runTests() {
|
||||
exclude: ['capability:orchestration'],
|
||||
}, null, 2));
|
||||
|
||||
const result = run([], { cwd: projectDir, homeDir });
|
||||
const result = run(['--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
assert.ok(fs.existsSync(path.join(homeDir, '.claude', 'skills', 'security-review', 'SKILL.md')));
|
||||
@@ -1210,7 +1211,7 @@ function runTests() {
|
||||
include: ['capability:security'],
|
||||
}, null, 2));
|
||||
|
||||
const result = run(['typescript'], { cwd: projectDir, homeDir });
|
||||
const result = run(['typescript', '--enable-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
|
||||
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||
@@ -1226,6 +1227,58 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('holds hook materialization without an explicit hook decision', () => {
|
||||
const projectDir = createTempDir('install-apply-consent-held-');
|
||||
const homeDir = createTempDir('install-apply-consent-held-home-');
|
||||
try {
|
||||
const result = run(['--profile', 'core'], { cwd: projectDir, homeDir });
|
||||
assert.notStrictEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('automatic hook runtime'));
|
||||
assert.ok(result.stderr.includes('--enable-hooks'));
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'hooks', 'hooks.json')));
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'ecc', 'install-state.json')));
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectDir);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('--no-hooks installs the profile without the hook runtime', () => {
|
||||
const projectDir = createTempDir('install-apply-no-hooks-');
|
||||
const homeDir = createTempDir('install-apply-no-hooks-home-');
|
||||
try {
|
||||
const result = run(['--profile', 'core', '--no-hooks'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'hooks', 'hooks.json')));
|
||||
const state = readJson(path.join(homeDir, '.claude', 'ecc', 'install-state.json'));
|
||||
assert.strictEqual(state.request.hookConsent, 'declined');
|
||||
assert.ok(!state.resolution.selectedModules.includes('hooks-runtime'));
|
||||
assert.ok(state.resolution.selectedModules.includes('rules-core'));
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectDir);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('rejects --enable-hooks combined with --no-hooks', () => {
|
||||
const result = run(['--profile', 'core', '--enable-hooks', '--no-hooks']);
|
||||
assert.notStrictEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('mutually exclusive'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('dry-run surfaces the pending hook decision as a warning', () => {
|
||||
const projectDir = createTempDir('install-apply-consent-dry-');
|
||||
const homeDir = createTempDir('install-apply-consent-dry-home-');
|
||||
try {
|
||||
const result = run(['--profile', 'core', '--dry-run'], { cwd: projectDir, homeDir });
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
assert.ok(result.stdout.includes('explicit hook decision'));
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectDir);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const {
|
||||
collectInteractiveOptions,
|
||||
main,
|
||||
parseArgs,
|
||||
printPlan,
|
||||
validateExecutionMode,
|
||||
} = require('../../scripts/install-guided');
|
||||
const {
|
||||
@@ -361,6 +362,28 @@ function runGuidedPtyFixture(answers) {
|
||||
assert.doesNotMatch(errorOutput.read(), /\[31m/);
|
||||
});
|
||||
|
||||
await test('printPlan discloses hook capabilities for non-off Claude hook profiles', async () => {
|
||||
let written = '';
|
||||
const output = { write: chunk => { written += chunk; } };
|
||||
printPlan({
|
||||
harnesses: [{ id: 'claude', channel: 'native-plugin' }],
|
||||
request: { harnesses: ['claude'], claudeHooks: 'standard' },
|
||||
}, output);
|
||||
assert.ok(written.includes("hook profile 'standard'"));
|
||||
assert.ok(written.includes('modify project source files'));
|
||||
assert.ok(written.includes("--claude-hooks off"));
|
||||
});
|
||||
|
||||
await test('printPlan omits the hook disclosure when Claude hooks are off', async () => {
|
||||
let written = '';
|
||||
const output = { write: chunk => { written += chunk; } };
|
||||
printPlan({
|
||||
harnesses: [{ id: 'claude', channel: 'native-plugin' }],
|
||||
request: { harnesses: ['claude'], claudeHooks: 'off' },
|
||||
}, output);
|
||||
assert.ok(!written.includes('enables automation'));
|
||||
});
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exitCode = failed > 0 ? 1 : 0;
|
||||
})();
|
||||
|
||||
@@ -126,6 +126,10 @@ function runTests() {
|
||||
readme.includes('--profile core --without baseline:hooks --target claude'),
|
||||
'README should document the hook opt-out path for the core profile'
|
||||
);
|
||||
assert.ok(
|
||||
readme.includes('./install.sh --profile core --no-hooks --target claude'),
|
||||
'README should document the explicit no-hooks consent path for the core profile'
|
||||
);
|
||||
assert.ok(
|
||||
readme.includes('This profile intentionally excludes `hooks-runtime`.'),
|
||||
'README should state that the minimal profile excludes hooks'
|
||||
|
||||
@@ -36,11 +36,11 @@ function runTests() {
|
||||
'README should warn against unsupported raw hook copying'
|
||||
);
|
||||
assert.ok(
|
||||
readme.includes('bash ./install.sh --target claude --modules hooks-runtime'),
|
||||
readme.includes('bash ./install.sh --target claude --modules hooks-runtime --enable-hooks'),
|
||||
'README should document the supported Bash hook install path'
|
||||
);
|
||||
assert.ok(
|
||||
readme.includes('pwsh -File .\\install.ps1 --target claude --modules hooks-runtime'),
|
||||
readme.includes('pwsh -File .\\install.ps1 --target claude --modules hooks-runtime --enable-hooks'),
|
||||
'README should document the supported PowerShell hook install path'
|
||||
);
|
||||
assert.ok(
|
||||
@@ -55,11 +55,11 @@ function runTests() {
|
||||
'hooks/README should warn against unsupported raw hook copying'
|
||||
);
|
||||
assert.ok(
|
||||
hooksReadme.includes('bash ./install.sh --target claude --modules hooks-runtime'),
|
||||
hooksReadme.includes('bash ./install.sh --target claude --modules hooks-runtime --enable-hooks'),
|
||||
'hooks/README should document the supported Bash hook install path'
|
||||
);
|
||||
assert.ok(
|
||||
hooksReadme.includes('pwsh -File .\\install.ps1 --target claude --modules hooks-runtime'),
|
||||
hooksReadme.includes('pwsh -File .\\install.ps1 --target claude --modules hooks-runtime --enable-hooks'),
|
||||
'hooks/README should document the supported PowerShell hook install path'
|
||||
);
|
||||
})) passed++; else failed++;
|
||||
|
||||
@@ -98,7 +98,7 @@ function runTests() {
|
||||
const projectRoot = createTempDir('repair-project-');
|
||||
|
||||
try {
|
||||
const installResult = runNode(INSTALL_SCRIPT, ['--target', 'cursor', 'typescript'], {
|
||||
const installResult = runNode(INSTALL_SCRIPT, ['--target', 'cursor', 'typescript', '--enable-hooks'], {
|
||||
cwd: projectRoot,
|
||||
homeDir,
|
||||
});
|
||||
@@ -137,6 +137,52 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('repair preserves a declined hook decision and does not reinstall hooks', () => {
|
||||
const homeDir = createTempDir('repair-home-');
|
||||
const projectRoot = createTempDir('repair-project-');
|
||||
|
||||
try {
|
||||
const installResult = runNode(INSTALL_SCRIPT, ['--target', 'cursor', '--profile', 'core', '--no-hooks'], {
|
||||
cwd: projectRoot,
|
||||
homeDir,
|
||||
});
|
||||
assert.strictEqual(installResult.code, 0, installResult.stderr);
|
||||
|
||||
const normalizedProjectRoot = fs.realpathSync(projectRoot);
|
||||
const managedPath = path.join(normalizedProjectRoot, '.cursor', 'rules', 'common-coding-style.mdc');
|
||||
const statePath = path.join(normalizedProjectRoot, '.cursor', 'ecc-install-state.json');
|
||||
const hooksConfigPath = path.join(normalizedProjectRoot, '.cursor', 'hooks.json');
|
||||
const expectedContent = fs.readFileSync(managedPath, 'utf8');
|
||||
fs.rmSync(managedPath, { force: true });
|
||||
|
||||
const doctorBefore = runNode(DOCTOR_SCRIPT, ['--target', 'cursor', '--json'], {
|
||||
cwd: projectRoot,
|
||||
homeDir,
|
||||
});
|
||||
assert.strictEqual(doctorBefore.code, 1);
|
||||
assert.ok(JSON.parse(doctorBefore.stdout).results[0].issues.some(issue => issue.code === 'missing-managed-files'));
|
||||
|
||||
const repairResult = runNode(REPAIR_SCRIPT, ['--target', 'cursor', '--json'], {
|
||||
cwd: projectRoot,
|
||||
homeDir,
|
||||
});
|
||||
assert.strictEqual(repairResult.code, 0, repairResult.stderr);
|
||||
|
||||
const parsed = JSON.parse(repairResult.stdout);
|
||||
assert.strictEqual(parsed.results[0].status, 'repaired');
|
||||
assert.ok(pathListIncludes(parsed.results[0].repairedPaths, managedPath));
|
||||
assert.strictEqual(fs.readFileSync(managedPath, 'utf8'), expectedContent);
|
||||
assert.ok(!fs.existsSync(hooksConfigPath));
|
||||
|
||||
const repairedState = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||
assert.strictEqual(repairedState.request.hookConsent, 'declined');
|
||||
assert.ok(!repairedState.resolution.selectedModules.includes('hooks-runtime'));
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('repairs drifted non-copy managed operations and refreshes install-state', () => {
|
||||
const homeDir = createTempDir('repair-home-');
|
||||
const projectRoot = createTempDir('repair-project-');
|
||||
|
||||
@@ -90,7 +90,7 @@ function runTests() {
|
||||
const projectRoot = createTempDir('uninstall-project-');
|
||||
|
||||
try {
|
||||
const installStdout = execFileSync('node', [INSTALL_SCRIPT, '--target', 'cursor', 'typescript'], {
|
||||
const installStdout = execFileSync('node', [INSTALL_SCRIPT, '--target', 'cursor', 'typescript', '--enable-hooks'], {
|
||||
cwd: projectRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
|
||||
Reference in New Issue
Block a user