mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 16:01:54 +00:00
Users who installed TORCH via `npm install <tarball> && npx torch-lock init` had no built-in way to cleanly remove it. This adds a `remove` command that detects and removes all TORCH artifacts: the torch/ directory, config files, runtime directories (.torch/, .scheduler-memory/, task-logs/), governance proposals, host package.json scripts, and the npm package itself. - Add cmdRemove() in src/ops.mjs with detection, preview, confirmation, and cleanup - Wire up `remove` command in CLI dispatcher (src/lib.mjs) - Add TORCH_HOST_SCRIPTS constant in constants.mjs for shared script key list - Add docs/removal.md with full manual removal instructions - Add removal section to README.md - Add 7 scenario tests in test/ops-remove.test.mjs https://claude.ai/code/session_011Snrxu3iZGLtJ9pnFSajpM
145 lines
6.1 KiB
JavaScript
145 lines
6.1 KiB
JavaScript
import { test, after } from 'node:test';
|
|
import assert from 'node:assert';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { cmdInit, cmdRemove } from '../src/ops.mjs';
|
|
|
|
const MOCK_CONFIG = {
|
|
installDir: 'torch',
|
|
namespace: 'test-namespace',
|
|
relays: ['wss://relay.damus.io']
|
|
};
|
|
|
|
const tempBase = fs.mkdtempSync(path.join(process.cwd(), 'test-ops-remove-'));
|
|
|
|
after(() => {
|
|
fs.rmSync(tempBase, { recursive: true, force: true });
|
|
});
|
|
|
|
test('cmdRemove reports nothing when no TORCH installation exists', async () => {
|
|
const projectRoot = path.join(tempBase, 'empty-project');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
// Create a basic package.json so it looks like a real project
|
|
fs.writeFileSync(path.join(projectRoot, 'package.json'), JSON.stringify({ name: 'my-app' }));
|
|
|
|
// Should complete without error — just reports nothing found
|
|
await cmdRemove(true, projectRoot);
|
|
|
|
// Project should be untouched
|
|
assert.ok(fs.existsSync(path.join(projectRoot, 'package.json')), 'package.json still exists');
|
|
});
|
|
|
|
test('cmdRemove with --force removes torch/ directory', async () => {
|
|
const projectRoot = path.join(tempBase, 'project-force');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
|
|
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
|
|
|
// Verify init created the expected artifacts
|
|
assert.ok(fs.existsSync(path.join(projectRoot, 'torch')), 'torch/ exists before remove');
|
|
assert.ok(fs.existsSync(path.join(projectRoot, 'torch-config.json')), 'torch-config.json exists before remove');
|
|
assert.ok(fs.existsSync(path.join(projectRoot, '.torch')), '.torch/ exists before remove');
|
|
|
|
await cmdRemove(true, projectRoot);
|
|
|
|
// All TORCH artifacts should be gone
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'torch')), 'torch/ removed');
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'torch-config.json')), 'torch-config.json removed');
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, '.torch')), '.torch/ removed');
|
|
});
|
|
|
|
test('cmdRemove removes runtime artifacts (task-logs, .scheduler-memory)', async () => {
|
|
const projectRoot = path.join(tempBase, 'project-runtime');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
|
|
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
|
|
|
// Simulate runtime artifacts that the scheduler would create
|
|
fs.mkdirSync(path.join(projectRoot, 'task-logs', 'daily'), { recursive: true });
|
|
fs.writeFileSync(path.join(projectRoot, 'task-logs', 'daily', 'run.md'), '# log');
|
|
fs.mkdirSync(path.join(projectRoot, '.scheduler-memory'), { recursive: true });
|
|
fs.writeFileSync(path.join(projectRoot, '.scheduler-memory', 'memory-store.json'), '{}');
|
|
|
|
await cmdRemove(true, projectRoot);
|
|
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'task-logs')), 'task-logs/ removed');
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, '.scheduler-memory')), '.scheduler-memory/ removed');
|
|
});
|
|
|
|
test('cmdRemove removes src/proposals/ and cleans empty src/', async () => {
|
|
const projectRoot = path.join(tempBase, 'project-proposals');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
|
|
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
|
|
|
// src/proposals/ is created by cmdInit
|
|
assert.ok(fs.existsSync(path.join(projectRoot, 'src', 'proposals')), 'src/proposals/ exists before remove');
|
|
|
|
await cmdRemove(true, projectRoot);
|
|
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'src', 'proposals')), 'src/proposals/ removed');
|
|
// src/ should also be removed since it's now empty
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'src')), 'empty src/ removed');
|
|
});
|
|
|
|
test('cmdRemove cleans torch:* scripts from host package.json', async () => {
|
|
const projectRoot = path.join(tempBase, 'project-scripts');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
|
|
// Create a host package.json with existing user scripts
|
|
fs.writeFileSync(path.join(projectRoot, 'package.json'), JSON.stringify({
|
|
name: 'my-app',
|
|
scripts: {
|
|
test: 'jest',
|
|
build: 'webpack'
|
|
}
|
|
}, null, 2));
|
|
|
|
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
|
|
|
// Verify init injected torch scripts
|
|
const pkgBefore = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
assert.ok('torch:dashboard' in pkgBefore.scripts, 'torch:dashboard injected');
|
|
assert.ok('torch:check' in pkgBefore.scripts, 'torch:check injected');
|
|
|
|
await cmdRemove(true, projectRoot);
|
|
|
|
// User scripts should remain, torch scripts should be gone
|
|
const pkgAfter = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
|
|
assert.strictEqual(pkgAfter.scripts.test, 'jest', 'user test script preserved');
|
|
assert.strictEqual(pkgAfter.scripts.build, 'webpack', 'user build script preserved');
|
|
assert.ok(!('torch:dashboard' in pkgAfter.scripts), 'torch:dashboard removed');
|
|
assert.ok(!('torch:check' in pkgAfter.scripts), 'torch:check removed');
|
|
assert.ok(!('torch:lock' in pkgAfter.scripts), 'torch:lock removed');
|
|
assert.ok(!('torch:health' in pkgAfter.scripts), 'torch:health removed');
|
|
assert.ok(!('torch:memory:list' in pkgAfter.scripts), 'torch:memory:list removed');
|
|
assert.ok(!('torch:memory:inspect' in pkgAfter.scripts), 'torch:memory:inspect removed');
|
|
});
|
|
|
|
test('cmdRemove cancels when user declines confirmation', async () => {
|
|
const projectRoot = path.join(tempBase, 'project-cancel');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
|
|
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
|
|
|
// Decline the confirmation
|
|
await cmdRemove(false, projectRoot, { confirm: false });
|
|
|
|
// Everything should still exist
|
|
assert.ok(fs.existsSync(path.join(projectRoot, 'torch')), 'torch/ still exists after cancel');
|
|
assert.ok(fs.existsSync(path.join(projectRoot, 'torch-config.json')), 'config still exists after cancel');
|
|
});
|
|
|
|
test('cmdRemove confirmed via mockAnswers removes artifacts', async () => {
|
|
const projectRoot = path.join(tempBase, 'project-confirm');
|
|
fs.mkdirSync(projectRoot, { recursive: true });
|
|
|
|
await cmdInit(false, projectRoot, MOCK_CONFIG);
|
|
|
|
// Confirm the removal
|
|
await cmdRemove(false, projectRoot, { confirm: true });
|
|
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'torch')), 'torch/ removed after confirm');
|
|
assert.ok(!fs.existsSync(path.join(projectRoot, 'torch-config.json')), 'config removed after confirm');
|
|
});
|