haraps 1.0.5 โ 1.0.6
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/SKILL.md +9 -1
- package/bin/index.js +1381 -756
- package/deploy.ps1 +414 -0
- package/deploy.sh +10 -0
- package/dev.ps1 +409 -0
- package/dev.sh +359 -0
- package/package.json +1 -1
package/dev.ps1
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# =============================================
|
|
2
|
+
# Medizys Local Development Runner
|
|
3
|
+
# =============================================
|
|
4
|
+
# Starts the Worker and Next.js frontend
|
|
5
|
+
# using concurrently in a single terminal window.
|
|
6
|
+
|
|
7
|
+
Write-Host ""
|
|
8
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
9
|
+
Write-Host " ๐ Medizys Local Dev Server" -ForegroundColor Cyan
|
|
10
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
11
|
+
Write-Host ""
|
|
12
|
+
Write-Host " Backend โ http://localhost:8787" -ForegroundColor Green
|
|
13
|
+
Write-Host " Frontend โ http://localhost:3000" -ForegroundColor Blue
|
|
14
|
+
Write-Host ""
|
|
15
|
+
Write-Host " Starting unified development environment..." -ForegroundColor Yellow
|
|
16
|
+
Write-Host "=============================================" -ForegroundColor Cyan
|
|
17
|
+
Write-Host ""
|
|
18
|
+
|
|
19
|
+
# Cleanup zombie worker processes and ports to prevent conflicts
|
|
20
|
+
Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
|
|
21
|
+
|
|
22
|
+
# Define processes to kill
|
|
23
|
+
$procNames = @("workerd", "node", "concurrently")
|
|
24
|
+
|
|
25
|
+
foreach ($procName in $procNames) {
|
|
26
|
+
$procs = Get-Process $procName -ErrorAction SilentlyContinue
|
|
27
|
+
if ($procs) {
|
|
28
|
+
Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
|
|
29
|
+
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Active verification loop to ensure all target processes have fully exited
|
|
34
|
+
$maxWaitSeconds = 5
|
|
35
|
+
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
|
36
|
+
while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
|
|
37
|
+
$remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
|
|
38
|
+
if (-not $remainingProcs) {
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
Start-Sleep -Milliseconds 200
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Function to robustly remove a directory with retry logic
|
|
45
|
+
function Remove-CacheDirectory($path, $label) {
|
|
46
|
+
if (Test-Path $path) {
|
|
47
|
+
Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
|
|
48
|
+
$attempts = 0
|
|
49
|
+
$maxAttempts = 5
|
|
50
|
+
$success = $false
|
|
51
|
+
|
|
52
|
+
while ($attempts -lt $maxAttempts -and -not $success) {
|
|
53
|
+
try {
|
|
54
|
+
Remove-Item -Recurse -Force $path -ErrorAction Stop
|
|
55
|
+
$success = $true
|
|
56
|
+
} catch {
|
|
57
|
+
$attempts++
|
|
58
|
+
if ($attempts -lt $maxAttempts) {
|
|
59
|
+
Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
|
|
60
|
+
Start-Sleep -Milliseconds (300 * $attempts)
|
|
61
|
+
} else {
|
|
62
|
+
Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Hard clear of wrangler state and Next.js cache to prevent Turbopack panics and corrupted dependency graphs
|
|
70
|
+
Remove-CacheDirectory "worker/.wrangler" "wrangler state"
|
|
71
|
+
Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
|
|
72
|
+
|
|
73
|
+
$ports = @(8787, 3000)
|
|
74
|
+
foreach ($port in $ports) {
|
|
75
|
+
Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
|
|
76
|
+
$retryCount = 0
|
|
77
|
+
while ($retryCount -lt 5) {
|
|
78
|
+
$netstatOutput = netstat -ano | Select-String ":$port\s+.*LISTENING\s+(\d+)"
|
|
79
|
+
if ($netstatOutput) {
|
|
80
|
+
$targetPid = $netstatOutput.Matches[0].Groups[1].Value
|
|
81
|
+
Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
|
|
82
|
+
Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
|
|
83
|
+
Start-Sleep -Seconds 1
|
|
84
|
+
$retryCount++
|
|
85
|
+
} else {
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Telegram watcher disabled by user request
|
|
92
|
+
# Write-Host " Starting Telegram watcher in background..." -ForegroundColor Magenta
|
|
93
|
+
# $watcherJob = Start-Job -ScriptBlock {
|
|
94
|
+
# & "$using:PSScriptRoot\scripts\telegram-watcher.ps1"
|
|
95
|
+
# }
|
|
96
|
+
# Write-Host " Telegram watcher running (Job ID: $($watcherJob.Id))" -ForegroundColor Magenta
|
|
97
|
+
# Write-Host " Reply to @mediisysbot on Telegram to queue instructions." -ForegroundColor DarkMagenta
|
|
98
|
+
# Write-Host ""
|
|
99
|
+
|
|
100
|
+
# Disable wrangler usage metrics prompt to prevent concurrently from hanging
|
|
101
|
+
$env:WRANGLER_SEND_METRICS = "false"
|
|
102
|
+
|
|
103
|
+
# Limit Node.js memory to prevent OOM crashes while providing 4GB for Next.js App Router
|
|
104
|
+
$env:NODE_OPTIONS = "--max-old-space-size=4096"
|
|
105
|
+
|
|
106
|
+
# Run the monorepo dev command through a Node.js wrapper to capture logs
|
|
107
|
+
# and listen for the 'c' keypress to copy output to the clipboard.
|
|
108
|
+
Write-Host "Starting development environment..." -ForegroundColor Green
|
|
109
|
+
$runnerScript = @'
|
|
110
|
+
const { spawn, execSync } = require('child_process');
|
|
111
|
+
let buffer = '';
|
|
112
|
+
let workerChild;
|
|
113
|
+
let frontendChild;
|
|
114
|
+
let lastRestartTime = 0;
|
|
115
|
+
const startTime = Date.now();
|
|
116
|
+
|
|
117
|
+
function formatSize(bytes) {
|
|
118
|
+
if (bytes < 1024) return bytes + 'B';
|
|
119
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
|
|
120
|
+
return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function formatUptime() {
|
|
124
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
125
|
+
const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
|
|
126
|
+
const s = (elapsed % 60).toString().padStart(2, '0');
|
|
127
|
+
const h = Math.floor(elapsed / 3600);
|
|
128
|
+
if (h > 0) return `${h}h ${m}m ${s}s`;
|
|
129
|
+
return `${m}m ${s}s`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let lastRows = 0;
|
|
133
|
+
let lastCols = 0;
|
|
134
|
+
|
|
135
|
+
function updateLegend() {
|
|
136
|
+
if (!process.stdout.isTTY) return;
|
|
137
|
+
const rows = process.stdout.rows || 30;
|
|
138
|
+
const cols = process.stdout.columns || 80;
|
|
139
|
+
|
|
140
|
+
// Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
|
|
141
|
+
if (rows !== lastRows || cols !== lastCols) {
|
|
142
|
+
process.stdout.write(`\x1b[1;${rows - 2}r`);
|
|
143
|
+
lastRows = rows;
|
|
144
|
+
lastCols = cols;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const lineCount = (buffer.match(/\n/g) || []).length;
|
|
148
|
+
const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
|
|
149
|
+
const uptimeStr = formatUptime();
|
|
150
|
+
const legend1 = ` [Copy] 'c': All (${lineCount}L / ${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors `;
|
|
151
|
+
const legend2 = ` [System] Uptime: ${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit `;
|
|
152
|
+
|
|
153
|
+
// Draw legend
|
|
154
|
+
process.stdout.write(`\x1b[s`); // save cursor
|
|
155
|
+
process.stdout.write(`\x1b[${rows - 1};1H\x1b[42m\x1b[30m${legend1.padEnd(cols)}\x1b[0m`);
|
|
156
|
+
process.stdout.write(`\x1b[${rows};1H\x1b[42m\x1b[30m${legend2.padEnd(cols)}\x1b[0m`);
|
|
157
|
+
process.stdout.write(`\x1b[u`); // restore cursor
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let legendInterval;
|
|
161
|
+
function startLegendUpdater() {
|
|
162
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
163
|
+
legendInterval = setInterval(() => {
|
|
164
|
+
updateLegend();
|
|
165
|
+
}, 1000);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function cleanupLegend() {
|
|
169
|
+
if (legendInterval) clearInterval(legendInterval);
|
|
170
|
+
if (!process.stdout.isTTY) return;
|
|
171
|
+
process.stdout.write('\x1b[r'); // reset scrolling region
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
process.stdout.on('resize', () => {
|
|
175
|
+
if (!process.stdout.isTTY) return;
|
|
176
|
+
cleanupLegend();
|
|
177
|
+
console.clear();
|
|
178
|
+
lastRows = 0;
|
|
179
|
+
updateLegend();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
function extractErrors(text) {
|
|
183
|
+
const lines = text.split('\n');
|
|
184
|
+
const errorPattern = /(error|exception|fail|โจฏ|โ|traceback)/i;
|
|
185
|
+
let inErrorBlock = false;
|
|
186
|
+
let blockStart = 0;
|
|
187
|
+
const blocks = [];
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
190
|
+
// Check for error keywords or stack trace lines
|
|
191
|
+
if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
|
|
192
|
+
if (!inErrorBlock) {
|
|
193
|
+
inErrorBlock = true;
|
|
194
|
+
blockStart = Math.max(0, i - 2); // 2 lines of context before
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
if (inErrorBlock) {
|
|
198
|
+
// If the next line is empty, we might just be between stack traces
|
|
199
|
+
if (lines[i].trim() === '') continue;
|
|
200
|
+
|
|
201
|
+
// End of error block
|
|
202
|
+
const blockEnd = Math.min(lines.length - 1, i + 2); // 2 lines of context after
|
|
203
|
+
blocks.push(lines.slice(blockStart, blockEnd + 1).join('\n'));
|
|
204
|
+
inErrorBlock = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (inErrorBlock) {
|
|
209
|
+
blocks.push(lines.slice(blockStart).join('\n'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return blocks.length > 0 ? blocks.join('\n\n--- [Next Error Block] ---\n\n') : '';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function formatPrefix(isError, source) {
|
|
216
|
+
const d = new Date();
|
|
217
|
+
const ts = String(d.getHours()).padStart(2, '0') + ':' +
|
|
218
|
+
String(d.getMinutes()).padStart(2, '0') + ':' +
|
|
219
|
+
String(d.getSeconds()).padStart(2, '0');
|
|
220
|
+
|
|
221
|
+
// Cyan for worker, Magenta for frontend
|
|
222
|
+
const color = source === 'worker' ? '\x1b[36m' : '\x1b[35m';
|
|
223
|
+
const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
|
|
224
|
+
return `\x1b[90m[${ts}]\x1b[0m ${color}${tag}\x1b[0m `;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function createStreamHandler(isError, source) {
|
|
228
|
+
let remainder = '';
|
|
229
|
+
return (d) => {
|
|
230
|
+
const chunk = remainder + d.toString();
|
|
231
|
+
const lines = chunk.split('\n');
|
|
232
|
+
remainder = lines.pop();
|
|
233
|
+
for (let line of lines) {
|
|
234
|
+
// Strip terminal-clearing and cursor-resetting escape codes
|
|
235
|
+
line = line.replace(/\x1b\[[23]J/g, '').replace(/\x1b\[[0-9;]*H/g, '').replace(/\x1bc/g, '');
|
|
236
|
+
// Strip cursor up/down and line clear codes
|
|
237
|
+
line = line.replace(/\x1b\[[0-9]*[A-G]/g, '').replace(/\x1b\[[0-9]*[K]/g, '');
|
|
238
|
+
// Strip carriage returns which cause overwrite from start of line
|
|
239
|
+
line = line.replace(/\r/g, '');
|
|
240
|
+
|
|
241
|
+
// Remove concurrently's prefix so we can add our own clean one
|
|
242
|
+
line = line.replace(/^\[[^\]]+\]\s*/, '');
|
|
243
|
+
|
|
244
|
+
if (line.trim() === '') continue;
|
|
245
|
+
|
|
246
|
+
const prefix = formatPrefix(isError, source);
|
|
247
|
+
const out = prefix + line + '\n';
|
|
248
|
+
|
|
249
|
+
if (isError) process.stderr.write(out);
|
|
250
|
+
else process.stdout.write(out);
|
|
251
|
+
buffer += out;
|
|
252
|
+
}
|
|
253
|
+
if (buffer.length > 1000000) {
|
|
254
|
+
const idx = buffer.indexOf('\n', buffer.length - 500000);
|
|
255
|
+
buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function startWorker() {
|
|
261
|
+
workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
|
|
262
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
263
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
264
|
+
windowsHide: true,
|
|
265
|
+
shell: true
|
|
266
|
+
});
|
|
267
|
+
workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
|
|
268
|
+
workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function startFrontend() {
|
|
272
|
+
frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
|
|
273
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
274
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
275
|
+
windowsHide: true,
|
|
276
|
+
shell: true
|
|
277
|
+
});
|
|
278
|
+
frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
|
|
279
|
+
frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function startServices() {
|
|
283
|
+
startWorker();
|
|
284
|
+
startFrontend();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.clear();
|
|
288
|
+
updateLegend();
|
|
289
|
+
startLegendUpdater();
|
|
290
|
+
startServices();
|
|
291
|
+
|
|
292
|
+
if (process.stdin.isTTY) {
|
|
293
|
+
process.stdin.setRawMode(true);
|
|
294
|
+
process.stdin.resume();
|
|
295
|
+
process.stdin.setEncoding('utf8');
|
|
296
|
+
process.stdin.on('data', k => {
|
|
297
|
+
const now = Date.now();
|
|
298
|
+
if (k === 'c' || k === 'C') {
|
|
299
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
300
|
+
if (!clean.trim()) {
|
|
301
|
+
console.log('\n\x1b[33mโ ๏ธ Buffer is empty.\x1b[0m\n');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
execSync('clip', { input: clean });
|
|
306
|
+
console.log('\n\x1b[32m๐ Copied ALL logs to clipboard!\x1b[0m\n');
|
|
307
|
+
} catch (e) {}
|
|
308
|
+
} else if (k === 's' || k === 'S') {
|
|
309
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
310
|
+
const shortLines = clean.split('\n').slice(-500).join('\n');
|
|
311
|
+
try {
|
|
312
|
+
execSync('clip', { input: shortLines });
|
|
313
|
+
console.log('\n\x1b[32m๐ Copied last 500 lines to clipboard!\x1b[0m\n');
|
|
314
|
+
} catch (e) {}
|
|
315
|
+
} else if (k === 'w' || k === 'W') {
|
|
316
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
317
|
+
const workerLogs = clean.split('\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\n');
|
|
318
|
+
try {
|
|
319
|
+
execSync('clip', { input: workerLogs });
|
|
320
|
+
console.log('\n\x1b[32m๐ Copied BACKEND logs to clipboard!\x1b[0m\n');
|
|
321
|
+
} catch (e) {}
|
|
322
|
+
} else if (k === 'f' || k === 'F') {
|
|
323
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
324
|
+
const frontendLogs = clean.split('\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\n');
|
|
325
|
+
try {
|
|
326
|
+
execSync('clip', { input: frontendLogs });
|
|
327
|
+
console.log('\n\x1b[32m๐ Copied FRONTEND logs to clipboard!\x1b[0m\n');
|
|
328
|
+
} catch (e) {}
|
|
329
|
+
} else if (k === 'e' || k === 'E') {
|
|
330
|
+
const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
331
|
+
const errors = extractErrors(clean);
|
|
332
|
+
if (errors) {
|
|
333
|
+
try {
|
|
334
|
+
execSync('clip', { input: errors });
|
|
335
|
+
console.log('\n\x1b[31m๐จ Errors copied to clipboard!\x1b[0m\n');
|
|
336
|
+
} catch (e) {}
|
|
337
|
+
} else {
|
|
338
|
+
console.log('\n\x1b[32mโ
No errors detected.\x1b[0m\n');
|
|
339
|
+
}
|
|
340
|
+
} else if (k === 'r' || k === 'R') {
|
|
341
|
+
if (now - lastRestartTime < 1000) return;
|
|
342
|
+
lastRestartTime = now;
|
|
343
|
+
console.log('\n\x1b[33m๐ Restarting ALL services...\x1b[0m\n');
|
|
344
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
345
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
346
|
+
startServices();
|
|
347
|
+
} else if (k === '1') {
|
|
348
|
+
if (now - lastRestartTime < 1000) return;
|
|
349
|
+
lastRestartTime = now;
|
|
350
|
+
console.log('\n\x1b[33m๐ Restarting FRONTEND...\x1b[0m\n');
|
|
351
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
352
|
+
startFrontend();
|
|
353
|
+
} else if (k === '2') {
|
|
354
|
+
if (now - lastRestartTime < 1000) return;
|
|
355
|
+
lastRestartTime = now;
|
|
356
|
+
console.log('\n\x1b[33m๐ Restarting BACKEND...\x1b[0m\n');
|
|
357
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
358
|
+
startWorker();
|
|
359
|
+
} else if (k === 'x' || k === 'X') {
|
|
360
|
+
console.clear();
|
|
361
|
+
buffer = '';
|
|
362
|
+
lastRows = 0;
|
|
363
|
+
updateLegend();
|
|
364
|
+
console.log('\x1b[32m๐งน Terminal cleared.\x1b[0m\n');
|
|
365
|
+
} else if (k === '\u0003' || k === 'q') {
|
|
366
|
+
console.log('\n\x1b[33mStopping services...\x1b[0m');
|
|
367
|
+
cleanupLegend();
|
|
368
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
369
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
370
|
+
process.exit();
|
|
371
|
+
} else if (k === 'Q') {
|
|
372
|
+
console.log('\n\x1b[31m๐งน Cleaning cache and dependencies, then stopping services...\x1b[0m');
|
|
373
|
+
cleanupLegend();
|
|
374
|
+
if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
375
|
+
if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
|
|
376
|
+
|
|
377
|
+
console.log('\x1b[33mRemoving node_modules, .next, and .wrangler...\x1b[0m');
|
|
378
|
+
const fs = require('fs');
|
|
379
|
+
const pathsToClean = ['frontend/.next', 'worker/.wrangler', 'frontend/node_modules', 'worker/node_modules', 'node_modules'];
|
|
380
|
+
for (const p of pathsToClean) {
|
|
381
|
+
try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
|
|
382
|
+
}
|
|
383
|
+
console.log('\x1b[32mCleanup complete.\x1b[0m');
|
|
384
|
+
process.exit();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Cleanup on unexpected exits
|
|
390
|
+
process.on('SIGINT', () => {
|
|
391
|
+
cleanupLegend();
|
|
392
|
+
process.exit();
|
|
393
|
+
});
|
|
394
|
+
process.on('exit', () => {
|
|
395
|
+
cleanupLegend();
|
|
396
|
+
console.clear();
|
|
397
|
+
});
|
|
398
|
+
'@
|
|
399
|
+
$runnerPath = Join-Path $env:TEMP "mediqueue-dev-runner.js"
|
|
400
|
+
Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
|
|
401
|
+
node $runnerPath
|
|
402
|
+
|
|
403
|
+
# Cleanup watcher on exit
|
|
404
|
+
if ($null -ne (Get-Variable -Name watcherJob -ErrorAction SilentlyContinue)) {
|
|
405
|
+
if ($watcherJob) {
|
|
406
|
+
Stop-Job -Job $watcherJob -ErrorAction SilentlyContinue
|
|
407
|
+
Remove-Job -Job $watcherJob -ErrorAction SilentlyContinue
|
|
408
|
+
}
|
|
409
|
+
}
|