commandmate 0.1.10 → 0.1.11
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.
- package/.env.example +8 -3
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +11 -11
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js +11 -12
- package/.next/server/app/api/external-apps/[id]/route.js +14 -15
- package/.next/server/app/api/external-apps/route.js +12 -13
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/slash-commands.body +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +7 -7
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +12 -13
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +3 -3
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +10 -10
- package/.next/server/chunks/1318.js +4 -4
- package/.next/server/chunks/1528.js +1 -1
- package/.next/server/chunks/7425.js +97 -48
- package/.next/server/chunks/9723.js +1 -1
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/src/middleware.js +2 -2
- package/.next/server/src/middleware.js.map +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-720605c2fb074444.js +1 -0
- package/.next/trace +5 -5
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +6 -4
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +64 -17
- package/dist/cli/commands/status.d.ts +4 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +95 -6
- package/dist/cli/commands/stop.d.ts +2 -0
- package/dist/cli/commands/stop.d.ts.map +1 -1
- package/dist/cli/commands/stop.js +27 -10
- package/dist/cli/index.js +16 -2
- package/dist/cli/types/index.d.ts +20 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/daemon-factory.d.ts +105 -0
- package/dist/cli/utils/daemon-factory.d.ts.map +1 -0
- package/dist/cli/utils/daemon-factory.js +117 -0
- package/dist/cli/utils/daemon.d.ts.map +1 -1
- package/dist/cli/utils/daemon.js +4 -0
- package/dist/cli/utils/env-setup.d.ts +24 -12
- package/dist/cli/utils/env-setup.d.ts.map +1 -1
- package/dist/cli/utils/env-setup.js +64 -43
- package/dist/cli/utils/input-validators.d.ts +103 -0
- package/dist/cli/utils/input-validators.d.ts.map +1 -0
- package/dist/cli/utils/input-validators.js +163 -0
- package/dist/cli/utils/install-context.d.ts +53 -0
- package/dist/cli/utils/install-context.d.ts.map +1 -0
- package/dist/cli/utils/install-context.js +96 -0
- package/dist/cli/utils/pid-manager.d.ts +34 -0
- package/dist/cli/utils/pid-manager.d.ts.map +1 -1
- package/dist/cli/utils/pid-manager.js +43 -0
- package/dist/cli/utils/port-allocator.d.ts +108 -0
- package/dist/cli/utils/port-allocator.d.ts.map +1 -0
- package/dist/cli/utils/port-allocator.js +166 -0
- package/dist/cli/utils/resource-resolvers.d.ts +92 -0
- package/dist/cli/utils/resource-resolvers.d.ts.map +1 -0
- package/dist/cli/utils/resource-resolvers.js +175 -0
- package/dist/cli/utils/worktree-detector.d.ts +82 -0
- package/dist/cli/utils/worktree-detector.d.ts.map +1 -0
- package/dist/cli/utils/worktree-detector.js +221 -0
- package/dist/lib/errors.d.ts +111 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +153 -0
- package/dist/server/server.js +3 -0
- package/dist/server/src/cli/utils/install-context.js +96 -0
- package/dist/server/src/config/system-directories.js +40 -0
- package/dist/server/src/lib/auto-yes-manager.js +325 -0
- package/dist/server/src/lib/auto-yes-resolver.js +34 -0
- package/dist/server/src/lib/cli-patterns.js +6 -1
- package/dist/server/src/lib/db-instance.js +12 -2
- package/dist/server/src/lib/db-migrations.js +68 -1
- package/dist/server/src/lib/db-path-resolver.js +99 -0
- package/dist/server/src/lib/db.js +21 -0
- package/dist/server/src/lib/env.js +52 -3
- package/dist/server/src/lib/worktrees.js +36 -1
- package/dist/server/src/types/external-apps.js +20 -0
- package/package.json +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-aea2d5e7e28955be.js +0 -1
- /package/.next/static/chunks/app/{page-96a8aa2ec30a44e9.js → page-fe35d61f14b90a51.js} +0 -0
- /package/.next/static/{8o5rUyZun0GklIHWDgkmv → gRNW5YXY43KqCKbCdaJoJ}/_buildManifest.js +0 -0
- /package/.next/static/{8o5rUyZun0GklIHWDgkmv → gRNW5YXY43KqCKbCdaJoJ}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Port Allocator
|
|
4
|
+
* Issue #136: Phase 1 - Foundation
|
|
5
|
+
*
|
|
6
|
+
* Provides automatic port allocation for worktree servers.
|
|
7
|
+
* SF-SEC-002: Implements MAX_WORKTREES limit to prevent port exhaustion attacks.
|
|
8
|
+
*
|
|
9
|
+
* @module port-allocator
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.PortAllocator = exports.MAX_WORKTREES = exports.DEFAULT_PORT_RANGE = void 0;
|
|
13
|
+
exports.createPortAllocator = createPortAllocator;
|
|
14
|
+
const net_1 = require("net");
|
|
15
|
+
const errors_1 = require("../../lib/errors");
|
|
16
|
+
/**
|
|
17
|
+
* Default port range for worktree servers
|
|
18
|
+
* Main server uses 3000, worktrees use 3001-3100
|
|
19
|
+
*/
|
|
20
|
+
exports.DEFAULT_PORT_RANGE = {
|
|
21
|
+
min: 3001,
|
|
22
|
+
max: 3100,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of concurrent worktree servers
|
|
26
|
+
* SF-SEC-002: Prevents port exhaustion attacks
|
|
27
|
+
*/
|
|
28
|
+
exports.MAX_WORKTREES = 10;
|
|
29
|
+
/**
|
|
30
|
+
* Port allocator for managing worktree server ports
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const allocator = new PortAllocator();
|
|
35
|
+
* const port = await allocator.findAvailablePort();
|
|
36
|
+
* allocator.markAllocated(port);
|
|
37
|
+
* // ... start server on port ...
|
|
38
|
+
* allocator.release(port);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
class PortAllocator {
|
|
42
|
+
static instance = null;
|
|
43
|
+
range;
|
|
44
|
+
allocatedPorts;
|
|
45
|
+
issuePortMap;
|
|
46
|
+
constructor(range = exports.DEFAULT_PORT_RANGE) {
|
|
47
|
+
this.range = range;
|
|
48
|
+
this.allocatedPorts = new Set();
|
|
49
|
+
this.issuePortMap = new Map();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get singleton instance
|
|
53
|
+
* Issue #136: Singleton pattern for CLI commands
|
|
54
|
+
*/
|
|
55
|
+
static getInstance() {
|
|
56
|
+
if (!PortAllocator.instance) {
|
|
57
|
+
PortAllocator.instance = new PortAllocator();
|
|
58
|
+
}
|
|
59
|
+
return PortAllocator.instance;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Allocate a port for a specific issue (synchronous, deterministic)
|
|
63
|
+
* Issue #136: Simple port allocation based on issue number
|
|
64
|
+
*
|
|
65
|
+
* @param issueNo - Issue number
|
|
66
|
+
* @returns Allocated port number
|
|
67
|
+
*/
|
|
68
|
+
allocate(issueNo) {
|
|
69
|
+
// Check if already allocated for this issue
|
|
70
|
+
const existing = this.issuePortMap.get(issueNo);
|
|
71
|
+
if (existing !== undefined) {
|
|
72
|
+
return existing;
|
|
73
|
+
}
|
|
74
|
+
// Deterministic port based on issue number (3001 + issueNo % 100)
|
|
75
|
+
const basePort = this.range.min + (issueNo % (this.range.max - this.range.min));
|
|
76
|
+
this.issuePortMap.set(issueNo, basePort);
|
|
77
|
+
this.allocatedPorts.add(basePort);
|
|
78
|
+
return basePort;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Find an available port in the configured range
|
|
82
|
+
*
|
|
83
|
+
* @returns Available port number
|
|
84
|
+
* @throws AppError with code PORT_EXHAUSTED if no ports available
|
|
85
|
+
* @throws AppError with code MAX_WORKTREES_EXCEEDED if limit reached
|
|
86
|
+
*/
|
|
87
|
+
async findAvailablePort() {
|
|
88
|
+
// Check MAX_WORKTREES limit
|
|
89
|
+
if (this.allocatedPorts.size >= exports.MAX_WORKTREES) {
|
|
90
|
+
throw new errors_1.AppError('MAX_WORKTREES_EXCEEDED', `Maximum number of worktrees (${exports.MAX_WORKTREES}) exceeded`, { currentCount: this.allocatedPorts.size, limit: exports.MAX_WORKTREES });
|
|
91
|
+
}
|
|
92
|
+
for (let port = this.range.min; port <= this.range.max; port++) {
|
|
93
|
+
// Skip already allocated ports
|
|
94
|
+
if (this.allocatedPorts.has(port)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (await this.isPortAvailable(port)) {
|
|
98
|
+
return port;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw new errors_1.AppError(errors_1.ErrorCode.PORT_EXHAUSTED, `No available ports in range ${this.range.min}-${this.range.max}`, { range: this.range, allocatedPorts: Array.from(this.allocatedPorts) });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if a specific port is available
|
|
105
|
+
*
|
|
106
|
+
* @param port - Port number to check
|
|
107
|
+
* @returns true if port is available
|
|
108
|
+
*/
|
|
109
|
+
async isPortAvailable(port) {
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
const server = (0, net_1.createServer)();
|
|
112
|
+
server.on('error', () => {
|
|
113
|
+
resolve(false);
|
|
114
|
+
});
|
|
115
|
+
server.listen(port, () => {
|
|
116
|
+
server.close(() => {
|
|
117
|
+
resolve(true);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Mark a port as allocated
|
|
124
|
+
*
|
|
125
|
+
* @param port - Port number to mark as allocated
|
|
126
|
+
*/
|
|
127
|
+
markAllocated(port) {
|
|
128
|
+
this.allocatedPorts.add(port);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Release an allocated port
|
|
132
|
+
*
|
|
133
|
+
* @param port - Port number to release
|
|
134
|
+
*/
|
|
135
|
+
release(port) {
|
|
136
|
+
this.allocatedPorts.delete(port);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get the count of allocated ports
|
|
140
|
+
*/
|
|
141
|
+
getAllocatedCount() {
|
|
142
|
+
return this.allocatedPorts.size;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if a port is currently allocated (in-memory tracking)
|
|
146
|
+
*
|
|
147
|
+
* @param port - Port number to check
|
|
148
|
+
* @returns true if port is allocated
|
|
149
|
+
*/
|
|
150
|
+
isAllocated(port) {
|
|
151
|
+
return this.allocatedPorts.has(port);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get all allocated ports
|
|
155
|
+
*/
|
|
156
|
+
getAllocatedPorts() {
|
|
157
|
+
return Array.from(this.allocatedPorts);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.PortAllocator = PortAllocator;
|
|
161
|
+
/**
|
|
162
|
+
* Create a port allocator with default settings
|
|
163
|
+
*/
|
|
164
|
+
function createPortAllocator(range) {
|
|
165
|
+
return new PortAllocator(range);
|
|
166
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource Path Resolvers
|
|
3
|
+
* Issue #136: Phase 1 - Task 1.2
|
|
4
|
+
*
|
|
5
|
+
* Provides Strategy pattern implementation for resolving resource paths:
|
|
6
|
+
* - Database paths (cm.db, cm-{issueNo}.db)
|
|
7
|
+
* - PID file paths (.commandmate.pid, pids/{issueNo}.pid)
|
|
8
|
+
* - Log file paths (commandmate.log, commandmate-{issueNo}.log)
|
|
9
|
+
*
|
|
10
|
+
* SF-SEC-001: TOCTOU protection via try-catch pattern in validate()
|
|
11
|
+
*
|
|
12
|
+
* @module resource-resolvers
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Interface for resource path resolvers
|
|
16
|
+
* Strategy pattern for unified resource path handling
|
|
17
|
+
*/
|
|
18
|
+
export interface ResourcePathResolver {
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the path for a resource
|
|
21
|
+
* @param issueNo - Optional issue number for worktree-specific resources
|
|
22
|
+
* @returns Absolute path to the resource
|
|
23
|
+
*/
|
|
24
|
+
resolve(issueNo?: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* Validate that a path is within the allowed directory
|
|
27
|
+
* SF-SEC-001: Uses try-catch pattern for TOCTOU protection
|
|
28
|
+
*
|
|
29
|
+
* @param targetPath - Path to validate
|
|
30
|
+
* @returns true if path is valid and within allowed boundaries
|
|
31
|
+
*/
|
|
32
|
+
validate(targetPath: string): boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Database path resolver
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const resolver = new DbPathResolver();
|
|
40
|
+
* resolver.resolve(); // ~/.commandmate/data/cm.db
|
|
41
|
+
* resolver.resolve(135); // ~/.commandmate/data/cm-135.db
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class DbPathResolver implements ResourcePathResolver {
|
|
45
|
+
resolve(issueNo?: number): string;
|
|
46
|
+
validate(targetPath: string): boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* PID file path resolver
|
|
50
|
+
*
|
|
51
|
+
* Maintains backward compatibility:
|
|
52
|
+
* - No issueNo: ~/.commandmate/.commandmate.pid (main server)
|
|
53
|
+
* - With issueNo: ~/.commandmate/pids/{issueNo}.pid (worktree server)
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const resolver = new PidPathResolver();
|
|
58
|
+
* resolver.resolve(); // ~/.commandmate/.commandmate.pid
|
|
59
|
+
* resolver.resolve(135); // ~/.commandmate/pids/135.pid
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare class PidPathResolver implements ResourcePathResolver {
|
|
63
|
+
resolve(issueNo?: number): string;
|
|
64
|
+
validate(targetPath: string): boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Log file path resolver
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const resolver = new LogPathResolver();
|
|
72
|
+
* resolver.resolve(); // ~/.commandmate/logs/commandmate.log
|
|
73
|
+
* resolver.resolve(135); // ~/.commandmate/logs/commandmate-135.log
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare class LogPathResolver implements ResourcePathResolver {
|
|
77
|
+
resolve(issueNo?: number): string;
|
|
78
|
+
validate(targetPath: string): boolean;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Factory function for DbPathResolver
|
|
82
|
+
*/
|
|
83
|
+
export declare function createDbPathResolver(): DbPathResolver;
|
|
84
|
+
/**
|
|
85
|
+
* Factory function for PidPathResolver
|
|
86
|
+
*/
|
|
87
|
+
export declare function createPidPathResolver(): PidPathResolver;
|
|
88
|
+
/**
|
|
89
|
+
* Factory function for LogPathResolver
|
|
90
|
+
*/
|
|
91
|
+
export declare function createLogPathResolver(): LogPathResolver;
|
|
92
|
+
//# sourceMappingURL=resource-resolvers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-resolvers.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/resource-resolvers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAElC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAe,YAAW,oBAAoB;IACzD,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAQjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAqBtC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAgB,YAAW,oBAAoB;IAC1D,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IASjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAqBtC;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAgB,YAAW,oBAAoB;IAC1D,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IASjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAqBtC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAErD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Resource Path Resolvers
|
|
4
|
+
* Issue #136: Phase 1 - Task 1.2
|
|
5
|
+
*
|
|
6
|
+
* Provides Strategy pattern implementation for resolving resource paths:
|
|
7
|
+
* - Database paths (cm.db, cm-{issueNo}.db)
|
|
8
|
+
* - PID file paths (.commandmate.pid, pids/{issueNo}.pid)
|
|
9
|
+
* - Log file paths (commandmate.log, commandmate-{issueNo}.log)
|
|
10
|
+
*
|
|
11
|
+
* SF-SEC-001: TOCTOU protection via try-catch pattern in validate()
|
|
12
|
+
*
|
|
13
|
+
* @module resource-resolvers
|
|
14
|
+
*/
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.LogPathResolver = exports.PidPathResolver = exports.DbPathResolver = void 0;
|
|
20
|
+
exports.createDbPathResolver = createDbPathResolver;
|
|
21
|
+
exports.createPidPathResolver = createPidPathResolver;
|
|
22
|
+
exports.createLogPathResolver = createLogPathResolver;
|
|
23
|
+
const fs_1 = require("fs");
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const install_context_1 = require("./install-context");
|
|
26
|
+
/**
|
|
27
|
+
* Database path resolver
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const resolver = new DbPathResolver();
|
|
32
|
+
* resolver.resolve(); // ~/.commandmate/data/cm.db
|
|
33
|
+
* resolver.resolve(135); // ~/.commandmate/data/cm-135.db
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
class DbPathResolver {
|
|
37
|
+
resolve(issueNo) {
|
|
38
|
+
const configDir = (0, install_context_1.getConfigDir)();
|
|
39
|
+
if (issueNo !== undefined) {
|
|
40
|
+
return path_1.default.join(configDir, 'data', `cm-${issueNo}.db`);
|
|
41
|
+
}
|
|
42
|
+
return path_1.default.join(configDir, 'data', 'cm.db');
|
|
43
|
+
}
|
|
44
|
+
validate(targetPath) {
|
|
45
|
+
const configDir = (0, install_context_1.getConfigDir)();
|
|
46
|
+
// SF-SEC-001: try-catch for TOCTOU protection
|
|
47
|
+
try {
|
|
48
|
+
const resolved = (0, fs_1.realpathSync)(targetPath);
|
|
49
|
+
return resolved.startsWith(configDir);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code === 'ENOENT') {
|
|
53
|
+
// New file - validate parent directory
|
|
54
|
+
const parentDir = path_1.default.dirname(targetPath);
|
|
55
|
+
try {
|
|
56
|
+
const resolvedParent = (0, fs_1.realpathSync)(parentDir);
|
|
57
|
+
return resolvedParent.startsWith(configDir);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.DbPathResolver = DbPathResolver;
|
|
68
|
+
/**
|
|
69
|
+
* PID file path resolver
|
|
70
|
+
*
|
|
71
|
+
* Maintains backward compatibility:
|
|
72
|
+
* - No issueNo: ~/.commandmate/.commandmate.pid (main server)
|
|
73
|
+
* - With issueNo: ~/.commandmate/pids/{issueNo}.pid (worktree server)
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const resolver = new PidPathResolver();
|
|
78
|
+
* resolver.resolve(); // ~/.commandmate/.commandmate.pid
|
|
79
|
+
* resolver.resolve(135); // ~/.commandmate/pids/135.pid
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
class PidPathResolver {
|
|
83
|
+
resolve(issueNo) {
|
|
84
|
+
const configDir = (0, install_context_1.getConfigDir)();
|
|
85
|
+
if (issueNo !== undefined) {
|
|
86
|
+
return path_1.default.join(configDir, 'pids', `${issueNo}.pid`);
|
|
87
|
+
}
|
|
88
|
+
// Backward compatibility: main PID file in config root
|
|
89
|
+
return path_1.default.join(configDir, '.commandmate.pid');
|
|
90
|
+
}
|
|
91
|
+
validate(targetPath) {
|
|
92
|
+
const configDir = (0, install_context_1.getConfigDir)();
|
|
93
|
+
// SF-SEC-001: try-catch for TOCTOU protection
|
|
94
|
+
try {
|
|
95
|
+
const resolved = (0, fs_1.realpathSync)(targetPath);
|
|
96
|
+
return resolved.startsWith(configDir);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT') {
|
|
100
|
+
// New file - validate parent directory
|
|
101
|
+
const parentDir = path_1.default.dirname(targetPath);
|
|
102
|
+
try {
|
|
103
|
+
const resolvedParent = (0, fs_1.realpathSync)(parentDir);
|
|
104
|
+
return resolvedParent.startsWith(configDir);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.PidPathResolver = PidPathResolver;
|
|
115
|
+
/**
|
|
116
|
+
* Log file path resolver
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* const resolver = new LogPathResolver();
|
|
121
|
+
* resolver.resolve(); // ~/.commandmate/logs/commandmate.log
|
|
122
|
+
* resolver.resolve(135); // ~/.commandmate/logs/commandmate-135.log
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
class LogPathResolver {
|
|
126
|
+
resolve(issueNo) {
|
|
127
|
+
const configDir = (0, install_context_1.getConfigDir)();
|
|
128
|
+
const logsDir = path_1.default.join(configDir, 'logs');
|
|
129
|
+
if (issueNo !== undefined) {
|
|
130
|
+
return path_1.default.join(logsDir, `commandmate-${issueNo}.log`);
|
|
131
|
+
}
|
|
132
|
+
return path_1.default.join(logsDir, 'commandmate.log');
|
|
133
|
+
}
|
|
134
|
+
validate(targetPath) {
|
|
135
|
+
const configDir = (0, install_context_1.getConfigDir)();
|
|
136
|
+
const logsDir = path_1.default.join(configDir, 'logs');
|
|
137
|
+
// SF-SEC-001: try-catch for TOCTOU protection
|
|
138
|
+
try {
|
|
139
|
+
const resolved = (0, fs_1.realpathSync)(targetPath);
|
|
140
|
+
return resolved.startsWith(logsDir);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error.code === 'ENOENT') {
|
|
144
|
+
// New file - validate logs directory exists within config
|
|
145
|
+
try {
|
|
146
|
+
const resolvedLogsDir = (0, fs_1.realpathSync)(logsDir);
|
|
147
|
+
return resolvedLogsDir.startsWith(configDir);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
exports.LogPathResolver = LogPathResolver;
|
|
158
|
+
/**
|
|
159
|
+
* Factory function for DbPathResolver
|
|
160
|
+
*/
|
|
161
|
+
function createDbPathResolver() {
|
|
162
|
+
return new DbPathResolver();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Factory function for PidPathResolver
|
|
166
|
+
*/
|
|
167
|
+
function createPidPathResolver() {
|
|
168
|
+
return new PidPathResolver();
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Factory function for LogPathResolver
|
|
172
|
+
*/
|
|
173
|
+
function createLogPathResolver() {
|
|
174
|
+
return new LogPathResolver();
|
|
175
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree Detector
|
|
3
|
+
* Issue #136: Phase 1 - Task 1.3
|
|
4
|
+
*
|
|
5
|
+
* Provides utilities for detecting git worktrees and extracting issue numbers.
|
|
6
|
+
* Used to automatically identify which worktree environment the CLI is running in.
|
|
7
|
+
*
|
|
8
|
+
* Security: Uses execFile instead of exec to prevent command injection.
|
|
9
|
+
*
|
|
10
|
+
* @module worktree-detector
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Information about a detected worktree
|
|
14
|
+
*/
|
|
15
|
+
export interface WorktreeInfo {
|
|
16
|
+
/** Issue number associated with the worktree */
|
|
17
|
+
issueNo: number;
|
|
18
|
+
/** Absolute path to the worktree root */
|
|
19
|
+
path: string;
|
|
20
|
+
/** Current git branch */
|
|
21
|
+
branch: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract issue number from a worktree directory path
|
|
25
|
+
*
|
|
26
|
+
* @param dirPath - Directory path to check
|
|
27
|
+
* @returns Issue number or null if not a worktree path
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* extractIssueNoFromPath('/home/user/repos/commandmate-issue-135'); // 135
|
|
32
|
+
* extractIssueNoFromPath('/home/user/repos/commandmate'); // null
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractIssueNoFromPath(dirPath: string): number | null;
|
|
36
|
+
/**
|
|
37
|
+
* Extract issue number from a git branch name
|
|
38
|
+
*
|
|
39
|
+
* @param branchName - Git branch name
|
|
40
|
+
* @returns Issue number or null if not found
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* extractIssueNoFromBranch('feature/135-add-worktree'); // 135
|
|
45
|
+
* extractIssueNoFromBranch('main'); // null
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function extractIssueNoFromBranch(branchName: string): number | null;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a directory is inside a git worktree
|
|
51
|
+
*
|
|
52
|
+
* @param dirPath - Directory path to check
|
|
53
|
+
* @returns true if directory is inside a git repository
|
|
54
|
+
*/
|
|
55
|
+
export declare function isWorktreeDirectory(dirPath: string): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Detect if the current directory is within a CommandMate worktree
|
|
58
|
+
*
|
|
59
|
+
* Attempts to extract issue number from:
|
|
60
|
+
* 1. Directory path (e.g., commandmate-issue-135)
|
|
61
|
+
* 2. Git branch name (e.g., feature/135-description)
|
|
62
|
+
*
|
|
63
|
+
* @param dirPath - Directory path to check (defaults to cwd)
|
|
64
|
+
* @returns WorktreeInfo if detected, null otherwise
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const info = await detectCurrentWorktree();
|
|
69
|
+
* if (info) {
|
|
70
|
+
* console.log(`Running in worktree for Issue #${info.issueNo}`);
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function detectCurrentWorktree(dirPath?: string): Promise<WorktreeInfo | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Detect worktree from environment or current directory
|
|
77
|
+
* Convenience function that checks CM_ISSUE_NO env var first
|
|
78
|
+
*
|
|
79
|
+
* @returns WorktreeInfo if detected, null otherwise
|
|
80
|
+
*/
|
|
81
|
+
export declare function detectWorktreeContext(): Promise<WorktreeInfo | null>;
|
|
82
|
+
//# sourceMappingURL=worktree-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree-detector.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/worktree-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAkBD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBrE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc1E;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY3E;AA6CD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkC1F;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAmB1E"}
|