mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-30 17:31:54 +00:00
Merge pull request #346 from PR0M3TH3AN/claude/add-torch-removal-command-keFx5
Add cmdRemove command to completely uninstall TORCH from projects
This commit is contained in:
7 files changed
+584
-3
No files matched your search
@@ -58,6 +58,9 @@ npx --no-install torch-lock init
|
||||
# Update TORCH configuration and scripts
|
||||
npx --no-install torch-lock update
|
||||
|
||||
# Remove TORCH completely from this project
|
||||
npx --no-install torch-lock remove
|
||||
|
||||
# Run the Dashboard
|
||||
npx --no-install torch-lock dashboard
|
||||
```
|
||||
@@ -113,6 +116,18 @@ By default, the dashboard binds to `127.0.0.1`. To allow external access, you ca
|
||||
|
||||
The dashboard supports Basic Authentication. You can enable it by setting the `TORCH_DASHBOARD_AUTH` environment variable or the `dashboard.auth` field in `torch-config.json` with the format `username:password`.
|
||||
|
||||
## Removing TORCH
|
||||
|
||||
To completely remove TORCH from a project:
|
||||
|
||||
```bash
|
||||
npx torch-lock remove
|
||||
```
|
||||
|
||||
This removes the `torch/` directory, configuration files, runtime artifacts, host package.json scripts, and the npm package. Use `--force` to skip the confirmation prompt.
|
||||
|
||||
For manual removal steps and a full list of what gets cleaned up, see [docs/removal.md](docs/removal.md).
|
||||
|
||||
## Environment variables
|
||||
|
||||
You can override configuration using environment variables:
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
# Removing TORCH from a Project
|
||||
|
||||
This guide covers how to completely remove TORCH from a project repository that installed it via the standard installation process (`npm install <torch-tarball> && npx torch-lock init`).
|
||||
|
||||
## Quick Removal (Recommended)
|
||||
|
||||
Run the built-in removal command from your project root:
|
||||
|
||||
```bash
|
||||
npx torch-lock remove
|
||||
```
|
||||
|
||||
This will:
|
||||
|
||||
1. Detect your TORCH installation (whether installed to `torch/` or to the project root)
|
||||
2. Show a list of all artifacts that will be removed
|
||||
3. Ask for confirmation before deleting anything
|
||||
4. Remove all TORCH files, directories, configuration, and the npm package
|
||||
|
||||
To skip the confirmation prompt:
|
||||
|
||||
```bash
|
||||
npx torch-lock remove --force
|
||||
```
|
||||
|
||||
## What Gets Removed
|
||||
|
||||
The `remove` command cleans up everything that `torch-lock init` created, plus any runtime artifacts:
|
||||
|
||||
| Artifact | Description |
|
||||
|---|---|
|
||||
| `torch/` | The main TORCH install directory (contains src, bin, dashboard, scripts, prompts, etc.) |
|
||||
| `torch-config.json` | TORCH configuration file at the project root |
|
||||
| `.torch/` | Hidden directory for prompt history tracking |
|
||||
| `.scheduler-memory/` | Runtime memory store used by the memory subsystem |
|
||||
| `task-logs/` | Scheduler run logs (daily/weekly cycle logs, relay health history) |
|
||||
| `src/proposals/` | Governance proposals directory |
|
||||
| `torch:*` scripts in `package.json` | Convenience scripts injected into the host project's package.json |
|
||||
| `node_modules/torch-lock/` | The installed npm package |
|
||||
|
||||
## Manual Removal
|
||||
|
||||
If you prefer to remove TORCH manually, or if the `remove` command is unavailable (e.g., the package was already uninstalled), follow these steps from your project root:
|
||||
|
||||
### 1. Remove the TORCH directory
|
||||
|
||||
```bash
|
||||
rm -rf torch/
|
||||
```
|
||||
|
||||
### 2. Remove TORCH configuration
|
||||
|
||||
```bash
|
||||
rm -f torch-config.json
|
||||
```
|
||||
|
||||
### 3. Remove hidden directories
|
||||
|
||||
```bash
|
||||
rm -rf .torch/
|
||||
rm -rf .scheduler-memory/
|
||||
```
|
||||
|
||||
### 4. Remove runtime logs
|
||||
|
||||
```bash
|
||||
rm -rf task-logs/
|
||||
```
|
||||
|
||||
### 5. Remove governance proposals (if empty/unused)
|
||||
|
||||
```bash
|
||||
# Only remove if this directory was created by TORCH and contains no user content
|
||||
rm -rf src/proposals/
|
||||
# Remove src/ too if it's now empty
|
||||
rmdir src/ 2>/dev/null
|
||||
```
|
||||
|
||||
### 6. Clean up package.json scripts
|
||||
|
||||
Remove these keys from the `"scripts"` section of your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"torch:dashboard": "...",
|
||||
"torch:check": "...",
|
||||
"torch:lock": "...",
|
||||
"torch:health": "...",
|
||||
"torch:memory:list": "...",
|
||||
"torch:memory:inspect": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Uninstall the npm package
|
||||
|
||||
```bash
|
||||
npm uninstall torch-lock
|
||||
```
|
||||
|
||||
### 8. Clean up environment variables (if set)
|
||||
|
||||
If you configured TORCH environment variables in your shell profile, CI, or `.env` files, remove them:
|
||||
|
||||
- `NOSTR_LOCK_NAMESPACE`
|
||||
- `NOSTR_LOCK_HASHTAG`
|
||||
- `NOSTR_LOCK_RELAYS`
|
||||
- `NOSTR_LOCK_TTL`
|
||||
- `NOSTR_LOCK_QUERY_TIMEOUT_MS`
|
||||
- `NOSTR_LOCK_DAILY_ROSTER`
|
||||
- `NOSTR_LOCK_WEEKLY_ROSTER`
|
||||
- `TORCH_CONFIG_PATH`
|
||||
- `TORCH_DASHBOARD_AUTH`
|
||||
- `TORCH_MEMORY_ENABLED`
|
||||
- `TORCH_MEMORY_INGEST_ENABLED`
|
||||
- `TORCH_MEMORY_RETRIEVAL_ENABLED`
|
||||
- `TORCH_MEMORY_PRUNE_ENABLED`
|
||||
- `AGENT_PLATFORM`
|
||||
|
||||
### 9. Optional: Remove generated reports
|
||||
|
||||
If you ran lock reliability reports, you may also want to remove:
|
||||
|
||||
```bash
|
||||
rm -rf artifacts/lock-reliability/
|
||||
```
|
||||
|
||||
## Verifying Complete Removal
|
||||
|
||||
After removal, verify no TORCH artifacts remain:
|
||||
|
||||
```bash
|
||||
# Should return no results
|
||||
ls torch/ 2>/dev/null
|
||||
ls .torch/ 2>/dev/null
|
||||
ls .scheduler-memory/ 2>/dev/null
|
||||
ls torch-config.json 2>/dev/null
|
||||
|
||||
# Should not contain torch-lock
|
||||
npm ls torch-lock 2>/dev/null
|
||||
|
||||
# Should not contain torch:* scripts
|
||||
grep '"torch:' package.json
|
||||
```
|
||||
|
||||
## Re-installing TORCH
|
||||
|
||||
If you want to re-install TORCH later:
|
||||
|
||||
```bash
|
||||
npm install https://github.com/PR0M3TH3AN/TORCH/archive/refs/heads/main.tar.gz && npx torch-lock init
|
||||
```
|
||||
+2
-1
@@ -68,7 +68,8 @@
|
||||
"test:ci-resilience": "node --test test/lock-acquisition-resilience.integration.test.mjs",
|
||||
"test:extended-main": "npm run test:integration:e2e && npm run test:ci-resilience",
|
||||
"torch:memory:list": "node bin/torch-lock.mjs list-memories",
|
||||
"torch:memory:inspect": "node bin/torch-lock.mjs inspect-memory"
|
||||
"torch:memory:inspect": "node bin/torch-lock.mjs inspect-memory",
|
||||
"torch:remove": "node bin/torch-lock.mjs remove"
|
||||
},
|
||||
"dependencies": {
|
||||
"nostr-tools": "2.23.1",
|
||||
|
||||
@@ -23,6 +23,19 @@ export const VALID_CADENCES = new Set(['daily', 'weekly']);
|
||||
export const KIND_APP_DATA = 30078;
|
||||
export const MS_PER_SECOND = 1000;
|
||||
|
||||
/**
|
||||
* Script keys injected into the host package.json during `torch-lock init`.
|
||||
* Used by both init (to add) and remove (to clean up).
|
||||
*/
|
||||
export const TORCH_HOST_SCRIPTS = [
|
||||
'torch:dashboard',
|
||||
'torch:check',
|
||||
'torch:lock',
|
||||
'torch:health',
|
||||
'torch:memory:list',
|
||||
'torch:memory:inspect',
|
||||
];
|
||||
|
||||
export const USAGE_TEXT = `Usage: torch-lock <command> [options]
|
||||
|
||||
Commands:
|
||||
@@ -34,6 +47,7 @@ Commands:
|
||||
dashboard [--port <port>] [--host <host>] Serve the dashboard (default: ${DEFAULT_DASHBOARD_PORT})
|
||||
init [--force] Initialize torch/ directory in current project
|
||||
update [--force] Update torch/ configuration (backups, merges)
|
||||
remove [--force] Remove all TORCH files and configuration from project
|
||||
|
||||
list-memories [--agent <id>] [--type <type>] [--tags <a,b>] [--pinned <true|false>] [--full]
|
||||
inspect-memory --id <memoryId>
|
||||
|
||||
+6
-1
@@ -20,7 +20,7 @@ import {
|
||||
USAGE_TEXT,
|
||||
MS_PER_SECOND,
|
||||
} from './constants.mjs';
|
||||
import { cmdInit, cmdUpdate } from './ops.mjs';
|
||||
import { cmdInit, cmdUpdate, cmdRemove } from './ops.mjs';
|
||||
import { parseArgs } from './cli-parser.mjs';
|
||||
import { getRoster as _getRoster } from './roster.mjs';
|
||||
import { queryLocks as _queryLocks, publishLock as _publishLock, parseLockEvent } from './lock-ops.mjs';
|
||||
@@ -637,6 +637,11 @@ export async function main(argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'remove': {
|
||||
await cmdRemove(args.force);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'list-memories': {
|
||||
const result = await listMemories({
|
||||
agent_id: args.agent,
|
||||
|
||||
+252
-1
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import crypto from 'node:crypto';
|
||||
import readline from 'node:readline/promises';
|
||||
import { stdin as input, stdout as output } from 'node:process';
|
||||
import { DEFAULT_RELAYS } from './constants.mjs';
|
||||
import { DEFAULT_RELAYS, TORCH_HOST_SCRIPTS } from './constants.mjs';
|
||||
import { ensureDir } from './utils.mjs';
|
||||
|
||||
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
@@ -401,6 +401,257 @@ function injectScriptsIntoHost(hostRoot, installDirName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the TORCH install directory within a project.
|
||||
*
|
||||
* Strategy:
|
||||
* 1. If a 'torch/' directory exists with a roster.json or bin/, it's the install dir.
|
||||
* 2. If the current directory's package.json has name "torch-lock", installed to '.'.
|
||||
* 3. Returns null if TORCH is not detected.
|
||||
*
|
||||
* @param {string} cwd - The project root directory
|
||||
* @returns {string|null} - The install directory name ('torch', '.', etc.) or null
|
||||
*/
|
||||
function detectTorchInstallDir(cwd) {
|
||||
// Check for 'torch/' subdirectory with TORCH markers
|
||||
const torchSubdir = path.join(cwd, 'torch');
|
||||
if (fs.existsSync(torchSubdir)) {
|
||||
const hasRoster = fs.existsSync(path.join(torchSubdir, 'roster.json'));
|
||||
const hasBin = fs.existsSync(path.join(torchSubdir, 'bin'));
|
||||
if (hasRoster || hasBin) {
|
||||
return 'torch';
|
||||
}
|
||||
}
|
||||
|
||||
// Check if installed to current directory
|
||||
const pkgPath = path.join(cwd, 'package.json');
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
if (pkg.name === 'torch-lock') {
|
||||
return '.';
|
||||
}
|
||||
} catch (_e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a directory or file if it exists, logging the action.
|
||||
*
|
||||
* @param {string} target - Absolute path to remove
|
||||
* @param {string} label - Human-readable label for log output
|
||||
* @param {Function} log - Logging function
|
||||
* @returns {boolean} - True if something was removed
|
||||
*/
|
||||
function removeIfExists(target, label, log) {
|
||||
if (!fs.existsSync(target)) return false;
|
||||
|
||||
const stat = fs.statSync(target);
|
||||
if (stat.isDirectory()) {
|
||||
fs.rmSync(target, { recursive: true, force: true });
|
||||
} else {
|
||||
fs.unlinkSync(target);
|
||||
}
|
||||
log(` Removed ${label}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes TORCH torch:* scripts from the host package.json.
|
||||
*
|
||||
* @param {string} hostRoot - Project root containing package.json
|
||||
* @param {Function} log - Logging function
|
||||
* @returns {boolean} - True if any scripts were removed
|
||||
*/
|
||||
function removeHostScripts(hostRoot, log) {
|
||||
const hostPkgPath = path.join(hostRoot, 'package.json');
|
||||
if (!fs.existsSync(hostPkgPath)) return false;
|
||||
|
||||
try {
|
||||
const raw = fs.readFileSync(hostPkgPath, 'utf8');
|
||||
const pkg = JSON.parse(raw);
|
||||
if (!pkg.scripts) return false;
|
||||
|
||||
let modified = false;
|
||||
for (const key of TORCH_HOST_SCRIPTS) {
|
||||
if (key in pkg.scripts) {
|
||||
delete pkg.scripts[key];
|
||||
log(` Removed script: "${key}"`);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
fs.writeFileSync(hostPkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
||||
log(' Updated package.json.');
|
||||
}
|
||||
return modified;
|
||||
} catch (e) {
|
||||
log(` Warning: Could not update package.json: ${e.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely removes TORCH from a project that installed it via
|
||||
* `npm install <torch-tarball> && npx torch-lock init`.
|
||||
*
|
||||
* What it removes:
|
||||
* 1. The torch/ install directory (or equivalent)
|
||||
* 2. torch-config.json at the project root
|
||||
* 3. .torch/ hidden directory (prompt history)
|
||||
* 4. .scheduler-memory/ directory (runtime memory store)
|
||||
* 5. task-logs/ directory (scheduler run logs)
|
||||
* 6. src/proposals/ directory (governance proposals)
|
||||
* 7. torch:* scripts from the host package.json
|
||||
* 8. The torch-lock npm package (node_modules/torch-lock)
|
||||
*
|
||||
* @param {boolean} force - If true, skip the confirmation prompt
|
||||
* @param {string} cwd - Working directory (project root)
|
||||
* @param {Object|null} mockAnswers - For testing: { confirm: true/false }
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function cmdRemove(force = false, cwd = process.cwd(), mockAnswers = null) {
|
||||
const log = console.log;
|
||||
const installDir = detectTorchInstallDir(cwd);
|
||||
|
||||
if (!installDir) {
|
||||
log('No TORCH installation detected in this directory.');
|
||||
log('Looked for:');
|
||||
log(' - A torch/ subdirectory containing roster.json or bin/');
|
||||
log(' - A package.json with name "torch-lock" (root install)');
|
||||
return;
|
||||
}
|
||||
|
||||
const isRootInstall = installDir === '.';
|
||||
const torchDir = path.resolve(cwd, installDir);
|
||||
|
||||
// Build a manifest of what will be removed
|
||||
const targets = [];
|
||||
|
||||
if (!isRootInstall && fs.existsSync(torchDir)) {
|
||||
targets.push({ path: torchDir, label: `${installDir}/ (TORCH install directory)` });
|
||||
}
|
||||
|
||||
const configPath = path.join(cwd, 'torch-config.json');
|
||||
if (fs.existsSync(configPath)) {
|
||||
targets.push({ path: configPath, label: 'torch-config.json' });
|
||||
}
|
||||
|
||||
const dotTorch = path.join(cwd, '.torch');
|
||||
if (fs.existsSync(dotTorch)) {
|
||||
targets.push({ path: dotTorch, label: '.torch/ (prompt history)' });
|
||||
}
|
||||
|
||||
const schedulerMemory = path.join(cwd, '.scheduler-memory');
|
||||
if (fs.existsSync(schedulerMemory)) {
|
||||
targets.push({ path: schedulerMemory, label: '.scheduler-memory/ (memory store)' });
|
||||
}
|
||||
|
||||
const taskLogs = path.join(cwd, 'task-logs');
|
||||
if (fs.existsSync(taskLogs)) {
|
||||
targets.push({ path: taskLogs, label: 'task-logs/ (scheduler logs)' });
|
||||
}
|
||||
|
||||
const proposals = path.join(cwd, 'src', 'proposals');
|
||||
if (fs.existsSync(proposals)) {
|
||||
targets.push({ path: proposals, label: 'src/proposals/ (governance proposals)' });
|
||||
}
|
||||
|
||||
// Check for host scripts
|
||||
const hostPkgPath = path.join(cwd, 'package.json');
|
||||
let hasHostScripts = false;
|
||||
if (fs.existsSync(hostPkgPath)) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(hostPkgPath, 'utf8'));
|
||||
if (pkg.scripts) {
|
||||
hasHostScripts = TORCH_HOST_SCRIPTS.some(key => key in pkg.scripts);
|
||||
}
|
||||
} catch (_e) { /* ignore */ }
|
||||
}
|
||||
|
||||
if (targets.length === 0 && !hasHostScripts) {
|
||||
log('Nothing to remove — no TORCH artifacts found.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show what will be removed
|
||||
log('\nThe following TORCH artifacts will be removed:\n');
|
||||
for (const t of targets) {
|
||||
log(` - ${t.label}`);
|
||||
}
|
||||
if (hasHostScripts) {
|
||||
log(' - torch:* scripts from package.json');
|
||||
}
|
||||
log(' - torch-lock npm package (via npm uninstall)');
|
||||
log('');
|
||||
|
||||
// Confirm unless --force
|
||||
if (!force) {
|
||||
let confirmed;
|
||||
|
||||
if (mockAnswers) {
|
||||
confirmed = mockAnswers.confirm;
|
||||
} else {
|
||||
const rl = readline.createInterface({ input, output });
|
||||
try {
|
||||
const answer = await rl.question('Proceed with removal? (yes/no): ');
|
||||
confirmed = answer.trim().toLowerCase() === 'yes';
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (!confirmed) {
|
||||
log('Removal cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log('\nRemoving TORCH...\n');
|
||||
|
||||
// 1. Remove directories and files
|
||||
for (const t of targets) {
|
||||
removeIfExists(t.path, t.label, log);
|
||||
}
|
||||
|
||||
// 2. Clean up empty src/ directory if proposals was the only thing in it
|
||||
const srcDir = path.join(cwd, 'src');
|
||||
if (fs.existsSync(srcDir)) {
|
||||
try {
|
||||
const entries = fs.readdirSync(srcDir);
|
||||
if (entries.length === 0) {
|
||||
fs.rmdirSync(srcDir);
|
||||
log(' Removed empty src/ directory');
|
||||
}
|
||||
} catch (_e) { /* leave it if not empty or permission error */ }
|
||||
}
|
||||
|
||||
// 3. Remove host package.json scripts
|
||||
if (hasHostScripts) {
|
||||
removeHostScripts(cwd, log);
|
||||
}
|
||||
|
||||
// 4. Uninstall the npm package
|
||||
log('\n Running npm uninstall torch-lock...');
|
||||
try {
|
||||
const { execSync } = await import('node:child_process');
|
||||
execSync('npm uninstall torch-lock', { cwd, stdio: 'pipe' });
|
||||
log(' Uninstalled torch-lock package.');
|
||||
} catch (e) {
|
||||
log(` Warning: npm uninstall torch-lock failed: ${e.message}`);
|
||||
log(' You may need to run "npm uninstall torch-lock" manually.');
|
||||
}
|
||||
|
||||
log('\nTORCH has been completely removed from this project.');
|
||||
log('If you used TORCH environment variables (NOSTR_LOCK_*, TORCH_*),');
|
||||
log('remember to remove them from your shell profile or CI configuration.');
|
||||
}
|
||||
|
||||
export function cmdUpdate(force = false, cwd = process.cwd()) {
|
||||
// Update logic needs to know WHERE torch is installed.
|
||||
// We can look for torch directory? Or assume 'torch'?
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
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');
|
||||
});
|
||||
Reference in new issue
Block a user