servherd 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CONTRIBUTING.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +653 -29
  4. package/dist/cli/commands/config.d.ts +35 -0
  5. package/dist/cli/commands/config.js +336 -0
  6. package/dist/cli/commands/info.d.ts +37 -0
  7. package/dist/cli/commands/info.js +98 -0
  8. package/dist/cli/commands/list.d.ts +26 -0
  9. package/dist/cli/commands/list.js +86 -0
  10. package/dist/cli/commands/logs.d.ts +46 -0
  11. package/dist/cli/commands/logs.js +292 -0
  12. package/dist/cli/commands/mcp.d.ts +5 -0
  13. package/dist/cli/commands/mcp.js +17 -0
  14. package/dist/cli/commands/refresh.d.ts +20 -0
  15. package/dist/cli/commands/refresh.js +139 -0
  16. package/dist/cli/commands/remove.d.ts +20 -0
  17. package/dist/cli/commands/remove.js +144 -0
  18. package/dist/cli/commands/restart.d.ts +25 -0
  19. package/dist/cli/commands/restart.js +177 -0
  20. package/dist/cli/commands/start.d.ts +37 -0
  21. package/dist/cli/commands/start.js +293 -0
  22. package/dist/cli/commands/stop.d.ts +20 -0
  23. package/dist/cli/commands/stop.js +108 -0
  24. package/dist/cli/index.d.ts +9 -0
  25. package/dist/cli/index.js +160 -0
  26. package/dist/cli/output/formatters.d.ts +117 -0
  27. package/dist/cli/output/formatters.js +454 -0
  28. package/dist/cli/output/json-formatter.d.ts +22 -0
  29. package/dist/cli/output/json-formatter.js +40 -0
  30. package/dist/index.d.ts +15 -0
  31. package/dist/index.js +25 -0
  32. package/dist/mcp/index.d.ts +14 -0
  33. package/dist/mcp/index.js +352 -0
  34. package/dist/mcp/resources/servers.d.ts +14 -0
  35. package/dist/mcp/resources/servers.js +128 -0
  36. package/dist/mcp/tools/config.d.ts +33 -0
  37. package/dist/mcp/tools/config.js +88 -0
  38. package/dist/mcp/tools/info.d.ts +36 -0
  39. package/dist/mcp/tools/info.js +65 -0
  40. package/dist/mcp/tools/list.d.ts +36 -0
  41. package/dist/mcp/tools/list.js +49 -0
  42. package/dist/mcp/tools/logs.d.ts +44 -0
  43. package/dist/mcp/tools/logs.js +55 -0
  44. package/dist/mcp/tools/refresh.d.ts +33 -0
  45. package/dist/mcp/tools/refresh.js +54 -0
  46. package/dist/mcp/tools/remove.d.ts +23 -0
  47. package/dist/mcp/tools/remove.js +43 -0
  48. package/dist/mcp/tools/restart.d.ts +23 -0
  49. package/dist/mcp/tools/restart.js +42 -0
  50. package/dist/mcp/tools/start.d.ts +38 -0
  51. package/dist/mcp/tools/start.js +73 -0
  52. package/dist/mcp/tools/stop.d.ts +23 -0
  53. package/dist/mcp/tools/stop.js +40 -0
  54. package/dist/services/config.service.d.ts +80 -0
  55. package/dist/services/config.service.js +227 -0
  56. package/dist/services/port.service.d.ts +82 -0
  57. package/dist/services/port.service.js +151 -0
  58. package/dist/services/process.service.d.ts +61 -0
  59. package/dist/services/process.service.js +220 -0
  60. package/dist/services/registry.service.d.ts +50 -0
  61. package/dist/services/registry.service.js +157 -0
  62. package/dist/types/config.d.ts +107 -0
  63. package/dist/types/config.js +44 -0
  64. package/dist/types/errors.d.ts +102 -0
  65. package/dist/types/errors.js +197 -0
  66. package/dist/types/pm2.d.ts +50 -0
  67. package/dist/types/pm2.js +4 -0
  68. package/dist/types/registry.d.ts +230 -0
  69. package/dist/types/registry.js +33 -0
  70. package/dist/utils/ci-detector.d.ts +31 -0
  71. package/dist/utils/ci-detector.js +68 -0
  72. package/dist/utils/config-drift.d.ts +71 -0
  73. package/dist/utils/config-drift.js +128 -0
  74. package/dist/utils/error-handler.d.ts +21 -0
  75. package/dist/utils/error-handler.js +38 -0
  76. package/dist/utils/log-follower.d.ts +10 -0
  77. package/dist/utils/log-follower.js +98 -0
  78. package/dist/utils/logger.d.ts +11 -0
  79. package/dist/utils/logger.js +24 -0
  80. package/dist/utils/names.d.ts +7 -0
  81. package/dist/utils/names.js +20 -0
  82. package/dist/utils/template.d.ts +88 -0
  83. package/dist/utils/template.js +180 -0
  84. package/dist/utils/time-parser.d.ts +19 -0
  85. package/dist/utils/time-parser.js +54 -0
  86. package/docs/ci-cd.md +408 -0
  87. package/docs/configuration.md +325 -0
  88. package/docs/mcp-integration.md +411 -0
  89. package/examples/basic-usage/README.md +187 -0
  90. package/examples/ci-github-actions/workflow.yml +195 -0
  91. package/examples/mcp-claude-code/README.md +213 -0
  92. package/examples/multi-server/README.md +270 -0
  93. package/examples/storybook/README.md +187 -0
  94. package/examples/vite-project/README.md +251 -0
  95. package/package.json +123 -6
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Error codes for servherd operations.
3
+ * Grouped by category:
4
+ * - 1xxx: Server-related errors
5
+ * - 2xxx: Port-related errors
6
+ * - 3xxx: PM2/Process-related errors
7
+ * - 4xxx: Configuration errors
8
+ * - 5xxx: Registry errors
9
+ * - 6xxx: Template errors
10
+ * - 7xxx: Command/CLI errors
11
+ * - 9xxx: Unknown/Other errors
12
+ */
13
+ export var ServherdErrorCode;
14
+ (function (ServherdErrorCode) {
15
+ // Server errors (1xxx)
16
+ ServherdErrorCode[ServherdErrorCode["SERVER_NOT_FOUND"] = 1001] = "SERVER_NOT_FOUND";
17
+ ServherdErrorCode[ServherdErrorCode["SERVER_ALREADY_EXISTS"] = 1002] = "SERVER_ALREADY_EXISTS";
18
+ ServherdErrorCode[ServherdErrorCode["SERVER_NOT_RUNNING"] = 1003] = "SERVER_NOT_RUNNING";
19
+ ServherdErrorCode[ServherdErrorCode["SERVER_ALREADY_RUNNING"] = 1004] = "SERVER_ALREADY_RUNNING";
20
+ // Port errors (2xxx)
21
+ ServherdErrorCode[ServherdErrorCode["PORT_UNAVAILABLE"] = 2001] = "PORT_UNAVAILABLE";
22
+ ServherdErrorCode[ServherdErrorCode["PORT_OUT_OF_RANGE"] = 2002] = "PORT_OUT_OF_RANGE";
23
+ ServherdErrorCode[ServherdErrorCode["PORT_ALLOCATION_FAILED"] = 2003] = "PORT_ALLOCATION_FAILED";
24
+ // PM2/Process errors (3xxx)
25
+ ServherdErrorCode[ServherdErrorCode["PM2_CONNECTION_FAILED"] = 3001] = "PM2_CONNECTION_FAILED";
26
+ ServherdErrorCode[ServherdErrorCode["PM2_START_FAILED"] = 3002] = "PM2_START_FAILED";
27
+ ServherdErrorCode[ServherdErrorCode["PM2_STOP_FAILED"] = 3003] = "PM2_STOP_FAILED";
28
+ ServherdErrorCode[ServherdErrorCode["PM2_DELETE_FAILED"] = 3004] = "PM2_DELETE_FAILED";
29
+ ServherdErrorCode[ServherdErrorCode["PM2_RESTART_FAILED"] = 3005] = "PM2_RESTART_FAILED";
30
+ ServherdErrorCode[ServherdErrorCode["PM2_DESCRIBE_FAILED"] = 3006] = "PM2_DESCRIBE_FAILED";
31
+ // Configuration errors (4xxx)
32
+ ServherdErrorCode[ServherdErrorCode["CONFIG_LOAD_FAILED"] = 4001] = "CONFIG_LOAD_FAILED";
33
+ ServherdErrorCode[ServherdErrorCode["CONFIG_SAVE_FAILED"] = 4002] = "CONFIG_SAVE_FAILED";
34
+ ServherdErrorCode[ServherdErrorCode["CONFIG_INVALID"] = 4003] = "CONFIG_INVALID";
35
+ ServherdErrorCode[ServherdErrorCode["CONFIG_KEY_NOT_FOUND"] = 4004] = "CONFIG_KEY_NOT_FOUND";
36
+ ServherdErrorCode[ServherdErrorCode["CONFIG_VALIDATION_FAILED"] = 4005] = "CONFIG_VALIDATION_FAILED";
37
+ // Registry errors (5xxx)
38
+ ServherdErrorCode[ServherdErrorCode["REGISTRY_LOAD_FAILED"] = 5001] = "REGISTRY_LOAD_FAILED";
39
+ ServherdErrorCode[ServherdErrorCode["REGISTRY_SAVE_FAILED"] = 5002] = "REGISTRY_SAVE_FAILED";
40
+ ServherdErrorCode[ServherdErrorCode["REGISTRY_CORRUPT"] = 5003] = "REGISTRY_CORRUPT";
41
+ // Template errors (6xxx)
42
+ ServherdErrorCode[ServherdErrorCode["TEMPLATE_INVALID"] = 6001] = "TEMPLATE_INVALID";
43
+ ServherdErrorCode[ServherdErrorCode["TEMPLATE_MISSING_VARIABLE"] = 6002] = "TEMPLATE_MISSING_VARIABLE";
44
+ // Command/CLI errors (7xxx)
45
+ ServherdErrorCode[ServherdErrorCode["COMMAND_INVALID"] = 7001] = "COMMAND_INVALID";
46
+ ServherdErrorCode[ServherdErrorCode["COMMAND_MISSING_ARGUMENT"] = 7002] = "COMMAND_MISSING_ARGUMENT";
47
+ ServherdErrorCode[ServherdErrorCode["COMMAND_CONFLICT"] = 7003] = "COMMAND_CONFLICT";
48
+ ServherdErrorCode[ServherdErrorCode["INTERACTIVE_NOT_AVAILABLE"] = 7004] = "INTERACTIVE_NOT_AVAILABLE";
49
+ // Unknown/Other errors (9xxx)
50
+ ServherdErrorCode[ServherdErrorCode["UNKNOWN_ERROR"] = 9999] = "UNKNOWN_ERROR";
51
+ })(ServherdErrorCode || (ServherdErrorCode = {}));
52
+ /**
53
+ * Map of error codes to their string names for display purposes.
54
+ */
55
+ const ERROR_CODE_NAMES = {
56
+ [ServherdErrorCode.SERVER_NOT_FOUND]: "SERVER_NOT_FOUND",
57
+ [ServherdErrorCode.SERVER_ALREADY_EXISTS]: "SERVER_ALREADY_EXISTS",
58
+ [ServherdErrorCode.SERVER_NOT_RUNNING]: "SERVER_NOT_RUNNING",
59
+ [ServherdErrorCode.SERVER_ALREADY_RUNNING]: "SERVER_ALREADY_RUNNING",
60
+ [ServherdErrorCode.PORT_UNAVAILABLE]: "PORT_UNAVAILABLE",
61
+ [ServherdErrorCode.PORT_OUT_OF_RANGE]: "PORT_OUT_OF_RANGE",
62
+ [ServherdErrorCode.PORT_ALLOCATION_FAILED]: "PORT_ALLOCATION_FAILED",
63
+ [ServherdErrorCode.PM2_CONNECTION_FAILED]: "PM2_CONNECTION_FAILED",
64
+ [ServherdErrorCode.PM2_START_FAILED]: "PM2_START_FAILED",
65
+ [ServherdErrorCode.PM2_STOP_FAILED]: "PM2_STOP_FAILED",
66
+ [ServherdErrorCode.PM2_DELETE_FAILED]: "PM2_DELETE_FAILED",
67
+ [ServherdErrorCode.PM2_RESTART_FAILED]: "PM2_RESTART_FAILED",
68
+ [ServherdErrorCode.PM2_DESCRIBE_FAILED]: "PM2_DESCRIBE_FAILED",
69
+ [ServherdErrorCode.CONFIG_LOAD_FAILED]: "CONFIG_LOAD_FAILED",
70
+ [ServherdErrorCode.CONFIG_SAVE_FAILED]: "CONFIG_SAVE_FAILED",
71
+ [ServherdErrorCode.CONFIG_INVALID]: "CONFIG_INVALID",
72
+ [ServherdErrorCode.CONFIG_KEY_NOT_FOUND]: "CONFIG_KEY_NOT_FOUND",
73
+ [ServherdErrorCode.CONFIG_VALIDATION_FAILED]: "CONFIG_VALIDATION_FAILED",
74
+ [ServherdErrorCode.REGISTRY_LOAD_FAILED]: "REGISTRY_LOAD_FAILED",
75
+ [ServherdErrorCode.REGISTRY_SAVE_FAILED]: "REGISTRY_SAVE_FAILED",
76
+ [ServherdErrorCode.REGISTRY_CORRUPT]: "REGISTRY_CORRUPT",
77
+ [ServherdErrorCode.TEMPLATE_INVALID]: "TEMPLATE_INVALID",
78
+ [ServherdErrorCode.TEMPLATE_MISSING_VARIABLE]: "TEMPLATE_MISSING_VARIABLE",
79
+ [ServherdErrorCode.COMMAND_INVALID]: "COMMAND_INVALID",
80
+ [ServherdErrorCode.COMMAND_MISSING_ARGUMENT]: "COMMAND_MISSING_ARGUMENT",
81
+ [ServherdErrorCode.COMMAND_CONFLICT]: "COMMAND_CONFLICT",
82
+ [ServherdErrorCode.INTERACTIVE_NOT_AVAILABLE]: "INTERACTIVE_NOT_AVAILABLE",
83
+ [ServherdErrorCode.UNKNOWN_ERROR]: "UNKNOWN_ERROR",
84
+ };
85
+ /**
86
+ * Custom error class for servherd operations.
87
+ * Includes error codes for programmatic handling and optional details.
88
+ */
89
+ export class ServherdError extends Error {
90
+ code;
91
+ details;
92
+ constructor(code, message, details) {
93
+ super(message);
94
+ this.name = "ServherdError";
95
+ this.code = code;
96
+ this.details = details;
97
+ // Maintains proper stack trace for where our error was thrown (V8 only)
98
+ if (Error.captureStackTrace) {
99
+ Error.captureStackTrace(this, ServherdError);
100
+ }
101
+ }
102
+ /**
103
+ * Serialize error to JSON for logging or API responses.
104
+ */
105
+ toJSON() {
106
+ return {
107
+ name: this.name,
108
+ code: this.code,
109
+ message: this.message,
110
+ details: this.details,
111
+ };
112
+ }
113
+ /**
114
+ * Get the string name of the error code.
115
+ */
116
+ getCodeName() {
117
+ return ERROR_CODE_NAMES[this.code] || "UNKNOWN_ERROR";
118
+ }
119
+ }
120
+ /**
121
+ * Type guard to check if an error is a ServherdError.
122
+ */
123
+ export function isServherdError(error) {
124
+ return error instanceof ServherdError;
125
+ }
126
+ /**
127
+ * Format an error for CLI display.
128
+ * Includes color codes and proper formatting for terminal output.
129
+ */
130
+ export function formatErrorForCLI(error) {
131
+ if (isServherdError(error)) {
132
+ let message = `\x1b[31mError [${error.code}]: ${error.message}\x1b[0m`;
133
+ if (error.details) {
134
+ if (error.details.stderr) {
135
+ message += `\n\x1b[90m${error.details.stderr}\x1b[0m`;
136
+ }
137
+ if (error.details.stdout) {
138
+ message += `\n\x1b[90m${error.details.stdout}\x1b[0m`;
139
+ }
140
+ }
141
+ return message;
142
+ }
143
+ if (error instanceof Error) {
144
+ return `\x1b[31mError: ${error.message}\x1b[0m`;
145
+ }
146
+ return "\x1b[31mUnknown error occurred\x1b[0m";
147
+ }
148
+ /**
149
+ * Format an error for MCP tool response.
150
+ * Returns a structured error response compatible with MCP protocol.
151
+ */
152
+ export function formatErrorForMCP(error) {
153
+ if (isServherdError(error)) {
154
+ return {
155
+ isError: true,
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: JSON.stringify({
160
+ error: error.getCodeName(),
161
+ code: error.code,
162
+ message: error.message,
163
+ details: error.details,
164
+ }, null, 2),
165
+ },
166
+ ],
167
+ };
168
+ }
169
+ if (error instanceof Error) {
170
+ return {
171
+ isError: true,
172
+ content: [
173
+ {
174
+ type: "text",
175
+ text: JSON.stringify({
176
+ error: "UNKNOWN_ERROR",
177
+ code: ServherdErrorCode.UNKNOWN_ERROR,
178
+ message: error.message,
179
+ }, null, 2),
180
+ },
181
+ ],
182
+ };
183
+ }
184
+ return {
185
+ isError: true,
186
+ content: [
187
+ {
188
+ type: "text",
189
+ text: JSON.stringify({
190
+ error: "UNKNOWN_ERROR",
191
+ code: ServherdErrorCode.UNKNOWN_ERROR,
192
+ message: "An unknown error occurred",
193
+ }, null, 2),
194
+ },
195
+ ],
196
+ };
197
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * PM2-related type definitions
3
+ */
4
+ export interface PM2StartOptions {
5
+ name: string;
6
+ script: string;
7
+ args?: string[];
8
+ cwd?: string;
9
+ env?: Record<string, string>;
10
+ instances?: number;
11
+ autorestart?: boolean;
12
+ watch?: boolean;
13
+ max_memory_restart?: string | number;
14
+ output?: string;
15
+ error?: string;
16
+ /** Format for log timestamps (moment.js format, e.g., "YYYY-MM-DD HH:mm:ss Z") */
17
+ log_date_format?: string;
18
+ }
19
+ export interface PM2ProcessEnv {
20
+ status: "online" | "stopped" | "errored" | "stopping" | "launching" | "one-launch-status";
21
+ pm_id: number;
22
+ name: string;
23
+ pm_uptime: number;
24
+ created_at: number;
25
+ restart_time: number;
26
+ unstable_restarts: number;
27
+ pm_cwd: string;
28
+ pm_exec_path: string;
29
+ exec_mode: "fork" | "cluster";
30
+ node_args: string[];
31
+ pm_out_log_path: string;
32
+ pm_err_log_path: string;
33
+ pm_pid_path: string;
34
+ env: Record<string, string>;
35
+ }
36
+ export interface PM2Monit {
37
+ memory: number;
38
+ cpu: number;
39
+ }
40
+ export interface PM2ProcessDescription {
41
+ pid: number;
42
+ name: string;
43
+ pm2_env: PM2ProcessEnv;
44
+ monit?: PM2Monit;
45
+ }
46
+ export interface PM2Process {
47
+ name: string;
48
+ pm_id: number;
49
+ }
50
+ export type PM2Callback<T> = (err: Error | null, result?: T) => void;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * PM2-related type definitions
3
+ */
4
+ export {};
@@ -0,0 +1,230 @@
1
+ import { z } from "zod";
2
+ export declare const ServerStatusSchema: z.ZodEnum<["online", "stopped", "errored", "unknown"]>;
3
+ /**
4
+ * Snapshot of config values used when server was started.
5
+ * Used to detect config drift.
6
+ */
7
+ export declare const ConfigSnapshotSchema: z.ZodObject<{
8
+ hostname: z.ZodOptional<z.ZodString>;
9
+ httpsCert: z.ZodOptional<z.ZodString>;
10
+ httpsKey: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ hostname?: string | undefined;
13
+ httpsCert?: string | undefined;
14
+ httpsKey?: string | undefined;
15
+ }, {
16
+ hostname?: string | undefined;
17
+ httpsCert?: string | undefined;
18
+ httpsKey?: string | undefined;
19
+ }>;
20
+ export type ConfigSnapshot = z.infer<typeof ConfigSnapshotSchema>;
21
+ export declare const ServerEntrySchema: z.ZodObject<{
22
+ id: z.ZodString;
23
+ name: z.ZodString;
24
+ command: z.ZodString;
25
+ resolvedCommand: z.ZodString;
26
+ cwd: z.ZodString;
27
+ port: z.ZodNumber;
28
+ protocol: z.ZodEnum<["http", "https"]>;
29
+ hostname: z.ZodString;
30
+ env: z.ZodRecord<z.ZodString, z.ZodString>;
31
+ createdAt: z.ZodString;
32
+ pm2Name: z.ZodString;
33
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
34
+ description: z.ZodOptional<z.ZodString>;
35
+ usedConfigKeys: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
36
+ configSnapshot: z.ZodOptional<z.ZodObject<{
37
+ hostname: z.ZodOptional<z.ZodString>;
38
+ httpsCert: z.ZodOptional<z.ZodString>;
39
+ httpsKey: z.ZodOptional<z.ZodString>;
40
+ }, "strip", z.ZodTypeAny, {
41
+ hostname?: string | undefined;
42
+ httpsCert?: string | undefined;
43
+ httpsKey?: string | undefined;
44
+ }, {
45
+ hostname?: string | undefined;
46
+ httpsCert?: string | undefined;
47
+ httpsKey?: string | undefined;
48
+ }>>;
49
+ }, "strip", z.ZodTypeAny, {
50
+ hostname: string;
51
+ protocol: "http" | "https";
52
+ id: string;
53
+ name: string;
54
+ command: string;
55
+ resolvedCommand: string;
56
+ cwd: string;
57
+ port: number;
58
+ env: Record<string, string>;
59
+ createdAt: string;
60
+ pm2Name: string;
61
+ tags?: string[] | undefined;
62
+ description?: string | undefined;
63
+ usedConfigKeys?: string[] | undefined;
64
+ configSnapshot?: {
65
+ hostname?: string | undefined;
66
+ httpsCert?: string | undefined;
67
+ httpsKey?: string | undefined;
68
+ } | undefined;
69
+ }, {
70
+ hostname: string;
71
+ protocol: "http" | "https";
72
+ id: string;
73
+ name: string;
74
+ command: string;
75
+ resolvedCommand: string;
76
+ cwd: string;
77
+ port: number;
78
+ env: Record<string, string>;
79
+ createdAt: string;
80
+ pm2Name: string;
81
+ tags?: string[] | undefined;
82
+ description?: string | undefined;
83
+ usedConfigKeys?: string[] | undefined;
84
+ configSnapshot?: {
85
+ hostname?: string | undefined;
86
+ httpsCert?: string | undefined;
87
+ httpsKey?: string | undefined;
88
+ } | undefined;
89
+ }>;
90
+ export declare const RegistrySchema: z.ZodObject<{
91
+ version: z.ZodString;
92
+ servers: z.ZodArray<z.ZodObject<{
93
+ id: z.ZodString;
94
+ name: z.ZodString;
95
+ command: z.ZodString;
96
+ resolvedCommand: z.ZodString;
97
+ cwd: z.ZodString;
98
+ port: z.ZodNumber;
99
+ protocol: z.ZodEnum<["http", "https"]>;
100
+ hostname: z.ZodString;
101
+ env: z.ZodRecord<z.ZodString, z.ZodString>;
102
+ createdAt: z.ZodString;
103
+ pm2Name: z.ZodString;
104
+ tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
105
+ description: z.ZodOptional<z.ZodString>;
106
+ usedConfigKeys: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
107
+ configSnapshot: z.ZodOptional<z.ZodObject<{
108
+ hostname: z.ZodOptional<z.ZodString>;
109
+ httpsCert: z.ZodOptional<z.ZodString>;
110
+ httpsKey: z.ZodOptional<z.ZodString>;
111
+ }, "strip", z.ZodTypeAny, {
112
+ hostname?: string | undefined;
113
+ httpsCert?: string | undefined;
114
+ httpsKey?: string | undefined;
115
+ }, {
116
+ hostname?: string | undefined;
117
+ httpsCert?: string | undefined;
118
+ httpsKey?: string | undefined;
119
+ }>>;
120
+ }, "strip", z.ZodTypeAny, {
121
+ hostname: string;
122
+ protocol: "http" | "https";
123
+ id: string;
124
+ name: string;
125
+ command: string;
126
+ resolvedCommand: string;
127
+ cwd: string;
128
+ port: number;
129
+ env: Record<string, string>;
130
+ createdAt: string;
131
+ pm2Name: string;
132
+ tags?: string[] | undefined;
133
+ description?: string | undefined;
134
+ usedConfigKeys?: string[] | undefined;
135
+ configSnapshot?: {
136
+ hostname?: string | undefined;
137
+ httpsCert?: string | undefined;
138
+ httpsKey?: string | undefined;
139
+ } | undefined;
140
+ }, {
141
+ hostname: string;
142
+ protocol: "http" | "https";
143
+ id: string;
144
+ name: string;
145
+ command: string;
146
+ resolvedCommand: string;
147
+ cwd: string;
148
+ port: number;
149
+ env: Record<string, string>;
150
+ createdAt: string;
151
+ pm2Name: string;
152
+ tags?: string[] | undefined;
153
+ description?: string | undefined;
154
+ usedConfigKeys?: string[] | undefined;
155
+ configSnapshot?: {
156
+ hostname?: string | undefined;
157
+ httpsCert?: string | undefined;
158
+ httpsKey?: string | undefined;
159
+ } | undefined;
160
+ }>, "many">;
161
+ }, "strip", z.ZodTypeAny, {
162
+ version: string;
163
+ servers: {
164
+ hostname: string;
165
+ protocol: "http" | "https";
166
+ id: string;
167
+ name: string;
168
+ command: string;
169
+ resolvedCommand: string;
170
+ cwd: string;
171
+ port: number;
172
+ env: Record<string, string>;
173
+ createdAt: string;
174
+ pm2Name: string;
175
+ tags?: string[] | undefined;
176
+ description?: string | undefined;
177
+ usedConfigKeys?: string[] | undefined;
178
+ configSnapshot?: {
179
+ hostname?: string | undefined;
180
+ httpsCert?: string | undefined;
181
+ httpsKey?: string | undefined;
182
+ } | undefined;
183
+ }[];
184
+ }, {
185
+ version: string;
186
+ servers: {
187
+ hostname: string;
188
+ protocol: "http" | "https";
189
+ id: string;
190
+ name: string;
191
+ command: string;
192
+ resolvedCommand: string;
193
+ cwd: string;
194
+ port: number;
195
+ env: Record<string, string>;
196
+ createdAt: string;
197
+ pm2Name: string;
198
+ tags?: string[] | undefined;
199
+ description?: string | undefined;
200
+ usedConfigKeys?: string[] | undefined;
201
+ configSnapshot?: {
202
+ hostname?: string | undefined;
203
+ httpsCert?: string | undefined;
204
+ httpsKey?: string | undefined;
205
+ } | undefined;
206
+ }[];
207
+ }>;
208
+ export type ServerStatus = z.infer<typeof ServerStatusSchema>;
209
+ export type ServerEntry = z.infer<typeof ServerEntrySchema>;
210
+ export type Registry = z.infer<typeof RegistrySchema>;
211
+ export interface ServerFilter {
212
+ name?: string;
213
+ tag?: string;
214
+ cwd?: string;
215
+ cmd?: string;
216
+ running?: boolean;
217
+ }
218
+ export interface AddServerOptions {
219
+ command: string;
220
+ cwd: string;
221
+ port: number;
222
+ name?: string;
223
+ protocol?: "http" | "https";
224
+ hostname?: string;
225
+ env?: Record<string, string>;
226
+ tags?: string[];
227
+ description?: string;
228
+ usedConfigKeys?: string[];
229
+ configSnapshot?: ConfigSnapshot;
230
+ }
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ export const ServerStatusSchema = z.enum(["online", "stopped", "errored", "unknown"]);
3
+ /**
4
+ * Snapshot of config values used when server was started.
5
+ * Used to detect config drift.
6
+ */
7
+ export const ConfigSnapshotSchema = z.object({
8
+ hostname: z.string().optional(),
9
+ httpsCert: z.string().optional(),
10
+ httpsKey: z.string().optional(),
11
+ });
12
+ export const ServerEntrySchema = z.object({
13
+ id: z.string(),
14
+ name: z.string(),
15
+ command: z.string(),
16
+ resolvedCommand: z.string(),
17
+ cwd: z.string(),
18
+ port: z.number().int().min(1).max(65535),
19
+ protocol: z.enum(["http", "https"]),
20
+ hostname: z.string(),
21
+ env: z.record(z.string()),
22
+ createdAt: z.string(),
23
+ pm2Name: z.string(),
24
+ tags: z.array(z.string()).optional(),
25
+ description: z.string().optional(),
26
+ // Config tracking for drift detection
27
+ usedConfigKeys: z.array(z.string()).optional(),
28
+ configSnapshot: ConfigSnapshotSchema.optional(),
29
+ });
30
+ export const RegistrySchema = z.object({
31
+ version: z.string(),
32
+ servers: z.array(ServerEntrySchema),
33
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CI Environment Detector
3
+ * Detects various CI/CD environments and provides information about them.
4
+ */
5
+ export interface CIInfo {
6
+ isCI: boolean;
7
+ name: string | null;
8
+ }
9
+ /**
10
+ * Options for CI detection that can be passed from CLI flags
11
+ */
12
+ export interface CIModeOptions {
13
+ ci?: boolean;
14
+ noCi?: boolean;
15
+ }
16
+ export declare class CIDetector {
17
+ /**
18
+ * Check if running in a CI environment
19
+ * @param options Optional CLI flags to override environment detection
20
+ */
21
+ static isCI(options?: CIModeOptions): boolean;
22
+ /**
23
+ * Get the name of the CI environment
24
+ * Returns null if not running in CI
25
+ */
26
+ static getCIName(): string | null;
27
+ /**
28
+ * Get complete CI information
29
+ */
30
+ static getInfo(): CIInfo;
31
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * CI Environment Detector
3
+ * Detects various CI/CD environments and provides information about them.
4
+ */
5
+ const CI_ENVIRONMENTS = [
6
+ { name: "GitHub Actions", envVar: "GITHUB_ACTIONS" },
7
+ { name: "GitLab CI", envVar: "GITLAB_CI" },
8
+ { name: "CircleCI", envVar: "CIRCLECI" },
9
+ { name: "Travis CI", envVar: "TRAVIS" },
10
+ { name: "Jenkins", envVar: "JENKINS_URL" },
11
+ { name: "Buildkite", envVar: "BUILDKITE" },
12
+ { name: "Azure Pipelines", envVar: "AZURE_PIPELINES" },
13
+ { name: "TeamCity", envVar: "TEAMCITY_VERSION" },
14
+ ];
15
+ export class CIDetector {
16
+ /**
17
+ * Check if running in a CI environment
18
+ * @param options Optional CLI flags to override environment detection
19
+ */
20
+ static isCI(options) {
21
+ // Explicit --no-ci flag takes highest precedence
22
+ if (options?.noCi) {
23
+ return false;
24
+ }
25
+ // Explicit --ci flag takes precedence over environment detection
26
+ if (options?.ci) {
27
+ return true;
28
+ }
29
+ // Fall back to environment variable detection
30
+ // Check for generic CI environment variable
31
+ if (process.env.CI) {
32
+ return true;
33
+ }
34
+ // Check for specific CI environments
35
+ for (const ci of CI_ENVIRONMENTS) {
36
+ if (process.env[ci.envVar]) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ /**
43
+ * Get the name of the CI environment
44
+ * Returns null if not running in CI
45
+ */
46
+ static getCIName() {
47
+ // Check for specific CI environments first
48
+ for (const ci of CI_ENVIRONMENTS) {
49
+ if (process.env[ci.envVar]) {
50
+ return ci.name;
51
+ }
52
+ }
53
+ // Check for generic CI environment variable
54
+ if (process.env.CI) {
55
+ return "Unknown CI";
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Get complete CI information
61
+ */
62
+ static getInfo() {
63
+ return {
64
+ isCI: this.isCI(),
65
+ name: this.getCIName(),
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Utilities for detecting configuration drift in servers.
3
+ * Drift occurs when config values have changed since a server was started.
4
+ */
5
+ import type { GlobalConfig } from "../types/config.js";
6
+ import type { ServerEntry, ConfigSnapshot } from "../types/registry.js";
7
+ /**
8
+ * Information about a drifted config value
9
+ */
10
+ export interface DriftedValue {
11
+ /** The config key that has drifted */
12
+ configKey: string;
13
+ /** The template variable name */
14
+ templateVar: string;
15
+ /** The value when server was started */
16
+ startedWith: string | undefined;
17
+ /** The current config value */
18
+ currentValue: string | undefined;
19
+ }
20
+ /**
21
+ * Result of drift detection for a server
22
+ */
23
+ export interface DriftResult {
24
+ /** Whether the server has config drift */
25
+ hasDrift: boolean;
26
+ /** List of drifted values */
27
+ driftedValues: DriftedValue[];
28
+ }
29
+ /**
30
+ * Extract the config keys used by a command template
31
+ * @param command - The command template string
32
+ * @returns Array of config keys used (e.g., ["hostname", "httpsCert"])
33
+ */
34
+ export declare function extractUsedConfigKeys(command: string): string[];
35
+ /**
36
+ * Create a config snapshot containing only the values used by a server
37
+ * @param config - Current global config
38
+ * @param usedConfigKeys - Config keys used by the server
39
+ * @returns Config snapshot with only relevant values
40
+ */
41
+ export declare function createConfigSnapshot(config: GlobalConfig, usedConfigKeys: string[]): ConfigSnapshot;
42
+ /**
43
+ * Detect config drift for a server
44
+ * @param server - Server entry from registry
45
+ * @param currentConfig - Current global config
46
+ * @returns Drift detection result
47
+ */
48
+ export declare function detectDrift(server: ServerEntry, currentConfig: GlobalConfig): DriftResult;
49
+ /**
50
+ * Find all servers that use a specific config key
51
+ * @param servers - Array of server entries
52
+ * @param configKey - The config key to search for
53
+ * @returns Array of servers using that config key
54
+ */
55
+ export declare function findServersUsingConfigKey(servers: ServerEntry[], configKey: string): ServerEntry[];
56
+ /**
57
+ * Find all servers with config drift
58
+ * @param servers - Array of server entries
59
+ * @param currentConfig - Current global config
60
+ * @returns Array of servers with drift and their drift details
61
+ */
62
+ export declare function findServersWithDrift(servers: ServerEntry[], currentConfig: GlobalConfig): Array<{
63
+ server: ServerEntry;
64
+ drift: DriftResult;
65
+ }>;
66
+ /**
67
+ * Format drift information for display
68
+ * @param drift - Drift result to format
69
+ * @returns Formatted string describing the drift
70
+ */
71
+ export declare function formatDrift(drift: DriftResult): string;