• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / servherd / 20961701317

13 Jan 2026 03:07PM UTC coverage: 82.727% (+1.2%) from 81.563%
20961701317

push

github

apowers313
test: improved test coverage to 81.56%

901 of 1027 branches covered (87.73%)

Branch coverage included in aggregate %.

3601 of 4415 relevant lines covered (81.56%)

13.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.74
/src/cli/commands/remove.ts
1
import { confirm } from "@inquirer/prompts";
1✔
2
import { RegistryService } from "../../services/registry.service.js";
3
import { ProcessService } from "../../services/process.service.js";
4
import type { ServerEntry } from "../../types/registry.js";
5
import { formatRemoveResult, type RemoveResult } from "../output/formatters.js";
6
import { formatAsJson, formatErrorAsJson } from "../output/json-formatter.js";
7
import { logger } from "../../utils/logger.js";
8
import { CIDetector } from "../../utils/ci-detector.js";
9
import { ServherdError, ServherdErrorCode } from "../../types/errors.js";
10

11
export interface RemoveCommandOptions {
12
  name?: string;
13
  all?: boolean;
14
  tag?: string;
15
  force?: boolean;
16
}
17

18
/**
19
 * Execute the remove command
20
 */
21
export async function executeRemove(options: RemoveCommandOptions): Promise<RemoveResult[]> {
19✔
22
  const registryService = new RegistryService();
19✔
23
  const processService = new ProcessService();
19✔
24
  const results: RemoveResult[] = [];
19✔
25

26
  try {
19✔
27
    await registryService.load();
19✔
28
    await processService.connect();
19✔
29

30
    let serversToRemove: ServerEntry[] = [];
19✔
31

32
    if (options.all) {
19✔
33
      serversToRemove = registryService.listServers();
9✔
34
    } else if (options.tag) {
19✔
35
      serversToRemove = registryService.listServers({ tag: options.tag });
1✔
36
    } else if (options.name) {
10✔
37
      const server = registryService.findByName(options.name);
9✔
38
      if (server) {
9✔
39
        serversToRemove = [server];
7✔
40
      } else {
9✔
41
        return [{
2✔
42
          name: options.name,
2✔
43
          success: false,
2✔
44
          message: `Server "${options.name}" not found in registry`,
2✔
45
        }];
2✔
46
      }
2✔
47
    }
9✔
48

49
    if (serversToRemove.length === 0) {
19✔
50
      return [];
1✔
51
    }
1✔
52

53
    // Ask for confirmation unless --force is specified
54
    if (!options.force) {
19✔
55
      // In CI mode, require --force to prevent hanging on confirmation prompt
56
      if (CIDetector.isCI()) {
6✔
57
        throw new ServherdError(
2✔
58
          ServherdErrorCode.INTERACTIVE_NOT_AVAILABLE,
2✔
59
          "Remove requires --force flag in CI mode to prevent hanging on confirmation prompt",
2✔
60
        );
2✔
61
      }
2✔
62

63
      const serverNames = serversToRemove.map((s) => s.name).join(", ");
4✔
64
      const message = serversToRemove.length === 1
4✔
65
        ? `Are you sure you want to remove server "${serversToRemove[0].name}"?`
4!
66
        : `Are you sure you want to remove ${serversToRemove.length} servers (${serverNames})?`;
×
67

68
      const confirmed = await confirm({ message });
6✔
69

70
      if (!confirmed) {
6✔
71
        return serversToRemove.map((server) => ({
1✔
72
          name: server.name,
1✔
73
          success: false,
1✔
74
          cancelled: true,
1✔
75
          message: "Cancelled by user",
1✔
76
        }));
1✔
77
      }
1✔
78
    }
6✔
79

80
    for (const server of serversToRemove) {
17✔
81
      try {
15✔
82
        // First try to delete from PM2
83
        let pm2DeleteFailed = false;
15✔
84
        try {
15✔
85
          await processService.delete(server.pm2Name);
15✔
86
        } catch (error) {
15✔
87
          // If PM2 delete fails due to process not found, we still want to remove from registry
88
          const message = error instanceof Error ? error.message : String(error);
5!
89
          if (!message.includes("not found") && !message.includes("process name not found")) {
5✔
90
            pm2DeleteFailed = true;
1✔
91
            results.push({
1✔
92
              name: server.name,
1✔
93
              success: false,
1✔
94
              message,
1✔
95
            });
1✔
96
            continue;
1✔
97
          }
1✔
98
          // Process not found in PM2 is okay - we'll still remove from registry
99
        }
5✔
100

101
        // Then remove from registry
102
        if (!pm2DeleteFailed) {
14✔
103
          await registryService.removeServer(server.id);
14✔
104
          results.push({
14✔
105
            name: server.name,
14✔
106
            success: true,
14✔
107
          });
14✔
108
        }
14✔
109
      } catch (error) {
15!
110
        const message = error instanceof Error ? error.message : String(error);
×
111
        results.push({
×
112
          name: server.name,
×
113
          success: false,
×
114
          message,
×
115
        });
×
116
      }
×
117
    }
15✔
118

119
    return results;
13✔
120
  } finally {
19✔
121
    processService.disconnect();
19✔
122
  }
19✔
123
}
19✔
124

125
/**
126
 * CLI action handler for remove command
127
 */
128
export async function removeAction(
×
129
  name: string | undefined,
×
130
  options: { all?: boolean; tag?: string; force?: boolean; json?: boolean },
×
131
): Promise<void> {
×
132
  try {
×
133
    if (!name && !options.all && !options.tag) {
×
134
      if (options.json) {
×
135
        console.log(formatErrorAsJson(new Error("Provide a server name, --all, or --tag")));
×
136
      } else {
×
137
        console.error("Error: Provide a server name, --all, or --tag");
×
138
      }
×
139
      process.exitCode = 1;
×
140
      return;
×
141
    }
×
142

143
    const results = await executeRemove({
×
144
      name,
×
145
      all: options.all,
×
146
      tag: options.tag,
×
147
      force: options.force,
×
148
    });
×
149

150
    if (options.json) {
×
151
      console.log(formatAsJson({ results }));
×
152
    } else {
×
153
      console.log(formatRemoveResult(results));
×
154
    }
×
155

156
    // Set exit code if any failures (excluding cancellations)
157
    if (results.some((r) => !r.success && !r.cancelled)) {
×
158
      process.exitCode = 1;
×
159
    }
×
160
  } catch (error) {
×
161
    if (options.json) {
×
162
      console.log(formatErrorAsJson(error));
×
163
    } else {
×
164
      const message = error instanceof Error ? error.message : String(error);
×
165
      console.error(`Error: ${message}`);
×
166
    }
×
167
    logger.error({ error }, "Remove command failed");
×
168
    process.exitCode = 1;
×
169
  }
×
170
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc