haraps 1.0.5 β†’ 1.0.7

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/bin/index.js CHANGED
@@ -1,760 +1,1390 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { execSync } = require('child_process');
6
-
7
- console.log('πŸš€ Scaffold Project Environment (Hara-XY) πŸš€');
8
-
9
- function ensureDir(dir) {
10
- if (!fs.existsSync(dir)) {
11
- fs.mkdirSync(dir, { recursive: true });
12
- }
13
- }
14
-
15
- function writeFile(filePath, content) {
16
- const fullPath = path.resolve(process.cwd(), filePath);
17
- ensureDir(path.dirname(fullPath));
18
- fs.writeFileSync(fullPath, content.trim() + '\n', 'utf8');
19
- console.log(`βœ… Created: ${filePath}`);
20
- }
21
-
22
- // 1. Dev Scripts
23
- writeFile('dev.sh', `
24
- #!/usr/bin/env bash
25
- # =============================================
26
- # Local Development Runner (Rust + Next.js)
27
- # =============================================
28
-
29
- echo -e "\\e[36m=============================================\\e[0m"
30
- echo -e "\\e[36m πŸš€ Local Dev Server (Rust + Next.js)\\e[0m"
31
- echo -e "\\e[36m=============================================\\e[0m"
32
-
33
- # Aggressive cleanup of ports 3000 (Next) and 8787 (Worker)
34
- for port in 8787 3000; do
35
- targetPid=$(lsof -ti :$port)
36
- if [ ! -z "$targetPid" ]; then
37
- echo -e "\\e[33m Freeing port $port (PID $targetPid)...\\e[0m"
38
- kill -9 $targetPid 2>/dev/null
39
- fi
40
- done
41
-
42
- # Run Next.js and Rust Worker concurrently
43
- npx concurrently \\
44
- -n "NEXT,RUST" \\
45
- -c "blue,green" \\
46
- "cd frontend && npm run dev" \\
47
- "cd backend && npx wrangler dev"
48
- `);
49
- fs.chmodSync(path.resolve(process.cwd(), 'dev.sh'), '755');
50
-
51
- writeFile('dev.ps1', `
52
- # =============================================
53
- # Project Local Development Runner
54
- # =============================================
55
- # Starts the Backend and Next.js frontend
56
- # using concurrently in a single terminal window.
57
-
58
- Write-Host ""
59
- Write-Host "=============================================" -ForegroundColor Cyan
60
- Write-Host " πŸš€ Project Local Dev Server" -ForegroundColor Cyan
61
- Write-Host "=============================================" -ForegroundColor Cyan
62
- Write-Host ""
63
- Write-Host " Backend β†’ http://localhost:8787" -ForegroundColor Green
64
- Write-Host " Frontend β†’ http://localhost:3000" -ForegroundColor Blue
65
- Write-Host ""
66
- Write-Host " Starting unified development environment..." -ForegroundColor Yellow
67
- Write-Host "=============================================" -ForegroundColor Cyan
68
- Write-Host ""
69
-
70
- # Cleanup zombie worker processes and ports to prevent conflicts
71
- Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
72
-
73
- # Define processes to kill
74
- $procNames = @("workerd", "node", "concurrently")
75
-
76
- foreach ($procName in $procNames) {
77
- $procs = Get-Process $procName -ErrorAction SilentlyContinue
78
- if ($procs) {
79
- Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
80
- $procs | Stop-Process -Force -ErrorAction SilentlyContinue
81
- }
82
- }
83
-
84
- # Active verification loop to ensure all target processes have fully exited
85
- $maxWaitSeconds = 5
86
- $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
87
- while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
88
- $remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
89
- if (-not $remainingProcs) {
90
- break
91
- }
92
- Start-Sleep -Milliseconds 200
93
- }
94
-
95
- # Function to robustly remove a directory with retry logic
96
- function Remove-CacheDirectory($path, $label) {
97
- if (Test-Path $path) {
98
- Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
99
- $attempts = 0
100
- $maxAttempts = 5
101
- $success = $false
102
-
103
- while ($attempts -lt $maxAttempts -and -not $success) {
104
- try {
105
- Remove-Item -Recurse -Force $path -ErrorAction Stop
106
- $success = $true
107
- } catch {
108
- $attempts++
109
- if ($attempts -lt $maxAttempts) {
110
- Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
111
- Start-Sleep -Milliseconds (300 * $attempts)
112
- } else {
113
- Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
114
- }
115
- }
116
- }
117
- }
118
- }
119
-
120
- # Hard clear of wrangler state and Next.js cache
121
- Remove-CacheDirectory "backend/.wrangler" "wrangler state"
122
- Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
123
-
124
- $ports = @(8787, 3000)
125
- foreach ($port in $ports) {
126
- Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
127
- $retryCount = 0
128
- while ($retryCount -lt 5) {
129
- $netstatOutput = netstat -ano | Select-String ":$port\s+.*LISTENING\s+(\d+)"
130
- if ($netstatOutput) {
131
- $targetPid = $netstatOutput.Matches[0].Groups[1].Value
132
- Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
133
- Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
134
- Start-Sleep -Seconds 1
135
- $retryCount++
136
- } else {
137
- break
138
- }
139
- }
140
- }
141
-
142
- $env:WRANGLER_SEND_METRICS = "false"
143
- $env:NODE_OPTIONS = "--max-old-space-size=4096"
144
-
145
- Write-Host "Starting development environment..." -ForegroundColor Green
146
- $runnerScript = @'
147
- const { spawn, execSync } = require('child_process');
148
- let buffer = '';
149
- let workerChild;
150
- let frontendChild;
151
- let lastRestartTime = 0;
152
- const startTime = Date.now();
153
-
154
- function formatSize(bytes) {
155
- if (bytes < 1024) return bytes + 'B';
156
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
157
- return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
158
- }
159
-
160
- function formatUptime() {
161
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
162
- const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
163
- const s = (elapsed % 60).toString().padStart(2, '0');
164
- const h = Math.floor(elapsed / 3600);
165
- if (h > 0) return \`\${h}h \${m}m \${s}s\`;
166
- return \`\${m}m \${s}s\`;
167
- }
168
-
169
- let lastRows = 0;
170
- let lastCols = 0;
171
-
172
- function updateLegend() {
173
- if (!process.stdout.isTTY) return;
174
- const rows = process.stdout.rows || 30;
175
- const cols = process.stdout.columns || 80;
176
-
177
- if (rows !== lastRows || cols !== lastCols) {
178
- process.stdout.write(\`\x1b[1;\${rows - 2}r\`);
179
- lastRows = rows;
180
- lastCols = cols;
181
- }
182
-
183
- const lineCount = (buffer.match(/\n/g) || []).length;
184
- const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
185
- const uptimeStr = formatUptime();
186
- const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
187
- const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit \`;
188
-
189
- process.stdout.write(\`\x1b[s\`);
190
- process.stdout.write(\`\x1b[\${rows - 1};1H\x1b[42m\x1b[30m\${legend1.padEnd(cols)}\x1b[0m\`);
191
- process.stdout.write(\`\x1b[\${rows};1H\x1b[42m\x1b[30m\${legend2.padEnd(cols)}\x1b[0m\`);
192
- process.stdout.write(\`\x1b[u\`);
193
- }
194
-
195
- let legendInterval;
196
- function startLegendUpdater() {
197
- if (legendInterval) clearInterval(legendInterval);
198
- legendInterval = setInterval(() => {
199
- updateLegend();
200
- }, 1000);
201
- }
202
-
203
- function cleanupLegend() {
204
- if (legendInterval) clearInterval(legendInterval);
205
- if (!process.stdout.isTTY) return;
206
- process.stdout.write('\x1b[r');
207
- }
208
-
209
- process.stdout.on('resize', () => {
210
- if (!process.stdout.isTTY) return;
211
- cleanupLegend();
212
- console.clear();
213
- lastRows = 0;
214
- updateLegend();
215
- });
216
-
217
- function extractErrors(text) {
218
- const lines = text.split('\n');
219
- const errorPattern = /(error|exception|fail|β¨―|βœ—|traceback)/i;
220
- let inErrorBlock = false;
221
- let blockStart = 0;
222
- const blocks = [];
223
-
224
- for (let i = 0; i < lines.length; i++) {
225
- if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
226
- if (!inErrorBlock) {
227
- inErrorBlock = true;
228
- blockStart = Math.max(0, i - 2);
229
- }
230
- } else {
231
- if (inErrorBlock) {
232
- if (lines[i].trim() === '') continue;
233
- const blockEnd = Math.min(lines.length - 1, i + 2);
234
- blocks.push(lines.slice(blockStart, blockEnd + 1).join('\n'));
235
- inErrorBlock = false;
236
- }
237
- }
238
- }
239
- if (inErrorBlock) {
240
- blocks.push(lines.slice(blockStart).join('\n'));
241
- }
242
- return blocks.length > 0 ? blocks.join('\n\n--- [Next Error Block] ---\n\n') : '';
243
- }
244
-
245
- function formatPrefix(isError, source) {
246
- const d = new Date();
247
- const ts = String(d.getHours()).padStart(2, '0') + ':' +
248
- String(d.getMinutes()).padStart(2, '0') + ':' +
249
- String(d.getSeconds()).padStart(2, '0');
250
- const color = source === 'worker' ? '\x1b[36m' : '\x1b[35m';
251
- const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
252
- return \`\x1b[90m[\${ts}]\x1b[0m \${color}\${tag}\x1b[0m \`;
253
- }
254
-
255
- function createStreamHandler(isError, source) {
256
- let remainder = '';
257
- return (d) => {
258
- const chunk = remainder + d.toString();
259
- const lines = chunk.split('\n');
260
- remainder = lines.pop();
261
- for (let line of lines) {
262
- line = line.replace(/\x1b\[[23]J/g, '').replace(/\x1b\[[0-9;]*H/g, '').replace(/\x1bc/g, '');
263
- line = line.replace(/\x1b\[[0-9]*[A-G]/g, '').replace(/\x1b\[[0-9]*[K]/g, '');
264
- line = line.replace(/\r/g, '');
265
- line = line.replace(/^\[[^\]]+\]\s*/, '');
266
-
267
- if (line.trim() === '') continue;
268
-
269
- const prefix = formatPrefix(isError, source);
270
- const out = prefix + line + '\n';
271
-
272
- if (isError) process.stderr.write(out);
273
- else process.stdout.write(out);
274
- buffer += out;
275
- }
276
- if (buffer.length > 1000000) {
277
- const idx = buffer.indexOf('\n', buffer.length - 500000);
278
- buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
279
- }
280
- };
281
- }
282
-
283
- function startWorker() {
284
- workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"cd backend && npx wrangler dev"'], {
285
- env: { ...process.env, FORCE_COLOR: '1' },
286
- stdio: ['ignore', 'pipe', 'pipe'],
287
- windowsHide: true,
288
- shell: true
289
- });
290
- workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
291
- workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
292
- }
293
-
294
- function startFrontend() {
295
- frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"cd frontend && npm run dev"'], {
296
- env: { ...process.env, FORCE_COLOR: '1' },
297
- stdio: ['ignore', 'pipe', 'pipe'],
298
- windowsHide: true,
299
- shell: true
300
- });
301
- frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
302
- frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
303
- }
304
-
305
- function startServices() {
306
- startWorker();
307
- startFrontend();
308
- }
309
-
310
- console.clear();
311
- updateLegend();
312
- startLegendUpdater();
313
- startServices();
314
-
315
- if (process.stdin.isTTY) {
316
- process.stdin.setRawMode(true);
317
- process.stdin.resume();
318
- process.stdin.setEncoding('utf8');
319
- process.stdin.on('data', k => {
320
- const now = Date.now();
321
- if (k === 'c' || k === 'C') {
322
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
323
- if (!clean.trim()) {
324
- console.log('\n\x1b[33m⚠️ Buffer is empty.\x1b[0m\n');
325
- return;
326
- }
327
- try {
328
- execSync('clip', { input: clean });
329
- console.log('\n\x1b[32mπŸ“‹ Copied ALL logs to clipboard!\x1b[0m\n');
330
- } catch (e) {}
331
- } else if (k === 's' || k === 'S') {
332
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
333
- const shortLines = clean.split('\n').slice(-500).join('\n');
334
- try {
335
- execSync('clip', { input: shortLines });
336
- console.log('\n\x1b[32mπŸ“‹ Copied last 500 lines to clipboard!\x1b[0m\n');
337
- } catch (e) {}
338
- } else if (k === 'w' || k === 'W') {
339
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
340
- const workerLogs = clean.split('\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\n');
341
- try {
342
- execSync('clip', { input: workerLogs });
343
- console.log('\n\x1b[32mπŸ“‹ Copied BACKEND logs to clipboard!\x1b[0m\n');
344
- } catch (e) {}
345
- } else if (k === 'f' || k === 'F') {
346
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
347
- const frontendLogs = clean.split('\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\n');
348
- try {
349
- execSync('clip', { input: frontendLogs });
350
- console.log('\n\x1b[32mπŸ“‹ Copied FRONTEND logs to clipboard!\x1b[0m\n');
351
- } catch (e) {}
352
- } else if (k === 'e' || k === 'E') {
353
- const clean = buffer.replace(/\x1B(?:[@-Z\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
354
- const errors = extractErrors(clean);
355
- if (errors) {
356
- try {
357
- execSync('clip', { input: errors });
358
- console.log('\n\x1b[31m🚨 Errors copied to clipboard!\x1b[0m\n');
359
- } catch (e) {}
360
- } else {
361
- console.log('\n\x1b[32mβœ… No errors detected.\x1b[0m\n');
362
- }
363
- } else if (k === 'r' || k === 'R') {
364
- if (now - lastRestartTime < 1000) return;
365
- lastRestartTime = now;
366
- console.log('\n\x1b[33mπŸ”„ Restarting ALL services...\x1b[0m\n');
367
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
368
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
369
- startServices();
370
- } else if (k === '1') {
371
- if (now - lastRestartTime < 1000) return;
372
- lastRestartTime = now;
373
- console.log('\n\x1b[33mπŸ”„ Restarting FRONTEND...\x1b[0m\n');
374
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
375
- startFrontend();
376
- } else if (k === '2') {
377
- if (now - lastRestartTime < 1000) return;
378
- lastRestartTime = now;
379
- console.log('\n\x1b[33mπŸ”„ Restarting BACKEND...\x1b[0m\n');
380
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
381
- startWorker();
382
- } else if (k === 'x' || k === 'X') {
383
- console.clear();
384
- buffer = '';
385
- lastRows = 0;
386
- updateLegend();
387
- console.log('\x1b[32m🧹 Terminal cleared.\x1b[0m\n');
388
- } else if (k === '\u0003' || k === 'q') {
389
- console.log('\n\x1b[33mStopping services...\x1b[0m');
390
- cleanupLegend();
391
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
392
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
393
- process.exit();
394
- } else if (k === 'Q') {
395
- console.log('\n\x1b[31m🧹 Cleaning cache and dependencies, then stopping services...\x1b[0m');
396
- cleanupLegend();
397
- if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
398
- if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
399
-
400
- console.log('\x1b[33mRemoving node_modules, .next, and .wrangler...\x1b[0m');
401
- const fs = require('fs');
402
- const pathsToClean = ['frontend/.next', 'backend/.wrangler', 'frontend/node_modules', 'backend/node_modules', 'node_modules'];
403
- for (const p of pathsToClean) {
404
- try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
405
- }
406
- console.log('\x1b[32mCleanup complete.\x1b[0m');
407
- process.exit();
408
- }
409
- });
410
- }
411
-
412
- process.on('SIGINT', () => {
413
- cleanupLegend();
414
- process.exit();
415
- });
416
- process.on('exit', () => {
417
- cleanupLegend();
418
- console.clear();
419
- });
420
- '@
421
- $runnerPath = Join-Path $env:TEMP "project-dev-runner.js"
422
- Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
423
- node $runnerPath
424
- `);
425
-
426
- // 2. Deploy Scripts
427
- writeFile('deploy.sh', `
428
- #!/usr/bin/env bash
429
- set -e
430
-
431
- echo -e "\\e[36m=============================================\\e[0m"
432
- echo -e "\\e[36m πŸš€ Deploying System (Rust + Next.js)\\e[0m"
433
- echo -e "\\e[36m=============================================\\e[0m"
434
-
435
- echo -e "\\e[32m[1/2] Deploying Rust Cloudflare Worker...\\e[0m"
436
- cd backend
437
- npx wrangler deploy
438
- cd ..
439
-
440
- echo -e "\\e[34m[2/2] Deploying Next.js Frontend...\\e[0m"
441
- cd frontend
442
- npm run build
443
- npx vercel deploy --prod
444
- cd ..
445
-
446
- echo -e "\\e[32mDeployment Complete!\\e[0m"
447
- `);
448
- fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
449
-
450
- writeFile('deploy.ps1', `
451
- param(
452
- [switch]$SkipFrontend = $false,
453
- [switch]$SkipBackend = $false,
454
- [switch]$Fast = $false
455
- )
456
-
457
- if ($Fast) {
458
- Write-Host "[FAST] Fast mode enabled." -ForegroundColor Cyan
459
- }
460
-
461
- $ErrorActionPreference = 'Stop'
462
- Set-Location $PSScriptRoot
463
-
464
- # Load .env files if present
465
- $envFiles = @(".env", ".env.local", "frontend\\.env.local")
466
- foreach ($file in $envFiles) {
467
- $envFile = Join-Path $PSScriptRoot $file
468
- if (Test-Path $envFile) {
469
- Write-Host "[RESILIENCE] Loading environment variables from $file..." -ForegroundColor Cyan
470
- Get-Content $envFile | Where-Object { $_ -match '^\s*([^#\s][^=]+)=(.*)' } | ForEach-Object {
471
- $name = $matches[1].Trim()
472
- $value = $matches[2].Trim()
473
- if ($value -match '^"(.*)"$') { $value = $matches[1] }
474
- elseif ($value -match "^'(.*)'$") { $value = $matches[1] }
475
- [Environment]::SetEnvironmentVariable($name, $value, "Process")
476
- }
477
- }
478
- }
479
-
480
- Write-Host "[RESILIENCE] Terminating conflicting processes to prevent locks..." -ForegroundColor Yellow
481
- $conflictingProcs = Get-WmiObject Win32_Process | Where-Object {
482
- $_.Name -match 'node.exe' -and
483
- ($_.CommandLine -match 'next build' -or $_.CommandLine -match 'tsc' -or $_.CommandLine -match 'wrangler deploy')
484
- }
485
- foreach ($p in $conflictingProcs) {
486
- Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
487
- }
488
-
489
- $GlobalReport = [PSCustomObject]@{
490
- StartTime = (Get-Date)
491
- Status = "Running"
492
- Steps = @()
493
- Error = $null
494
- }
495
-
496
- $ReportsDir = Join-Path $PSScriptRoot "deployment_reports"
497
- if (!(Test-Path $ReportsDir)) { New-Item -ItemType Directory -Path $ReportsDir | Out-Null }
498
-
499
- function Add-Step {
500
- param([string]$Name, [string]$Status, [string]$Details = "")
501
- $GlobalReport.Steps += [PSCustomObject]@{
502
- Name = $Name
503
- Status = $Status
504
- Details = $Details
505
- Time = (Get-Date).ToString("T")
506
- }
507
- }
508
-
509
- function Generate-FinalReport {
510
- param([string]$FinalStatus)
511
- $GlobalReport.Status = $FinalStatus
512
-
513
- $reportText = "=================================================\`n"
514
- $reportText += "PROJECT DEPLOYMENT REPORT\`n"
515
- $reportText += "=================================================\`n"
516
- $reportText += "Status: $FinalStatus\`n"
517
- $reportText += "Duration: $((Get-Date) - $GlobalReport.StartTime)\`n"
518
- $reportText += "-------------------------------------------------\`n"
519
-
520
- foreach ($step in $GlobalReport.Steps) {
521
- $indicator = if ($step.Status -eq "SUCCESS") { "[OK]" } else { "[FAIL]" }
522
- $reportText += "$indicator [$($step.Time)] $($step.Name): $($step.Status)\`n"
523
- if ($step.Details) { $reportText += " > $($step.Details)\`n" }
524
- }
525
-
526
- if ($GlobalReport.Error) {
527
- $reportText += "-------------------------------------------------\`n"
528
- $reportText += "CRITICAL ERROR:\`n$($GlobalReport.Error)\`n"
529
- }
530
- $reportText += "=================================================\`n"
531
-
532
- $statusIndicator = if ($FinalStatus -eq "SUCCESS") { "SUCCESS" } else { "FAILED" }
533
- $humanTime = (Get-Date).ToString("ddMMM_hh-mmtt")
534
- $fileName = "Deploy_\${statusIndicator}_\${humanTime}.txt"
535
- $filePath = Join-Path $ReportsDir $fileName
536
- $reportText | Out-File -FilePath $filePath
537
-
538
- try { $reportText | Set-Clipboard } catch {}
539
- return $reportText
540
- }
541
-
542
- function Invoke-ModernCommand {
543
- param([string]$Step, [scriptblock]$Script, [switch]$NoExit)
544
- Write-Host ""
545
- Write-Host "[STEP] $($Step)..." -ForegroundColor Yellow
546
- try {
547
- $global:LASTEXITCODE = 0
548
- & $Script
549
- $exitCode = $LASTEXITCODE
550
-
551
- if ($exitCode -eq 0) {
552
- Add-Step $Step "SUCCESS"
553
- Write-Host "[OK] $Step completed successfully." -ForegroundColor Green
554
- } else {
555
- throw "Command failed with exit code $exitCode."
556
- }
557
- } catch {
558
- $errorMsg = $_.ToString()
559
- Add-Step $Step "FAILED" "Critical Error: See Global Report"
560
- $GlobalReport.Error = "Step: $Step\`nError: $errorMsg"
561
-
562
- if ($NoExit) {
563
- Write-Host "[WARN] $Step failed, but continuing." -ForegroundColor Yellow
564
- throw $errorMsg
565
- }
566
-
567
- $finalReport = Generate-FinalReport "FAILED"
568
- Write-Host "\`nDEPLOYMENT FAILED!\`n$errorMsg" -ForegroundColor Red
569
- Write-Host "[CLIPBOARD] Full error report copied to your clipboard!" -ForegroundColor Cyan
570
-
571
- Set-Location $PSScriptRoot
572
- exit 1
573
- }
574
- }
575
-
576
- if (!$SkipBackend) {
577
- try {
578
- Push-Location backend
579
- Invoke-ModernCommand "Rust Backend Deployment" { npx wrangler deploy }
580
- } finally {
581
- Pop-Location
582
- }
583
- } else {
584
- Add-Step "Backend Deployment" "SKIPPED"
585
- }
586
-
587
- if (!$SkipFrontend) {
588
- try {
589
- Push-Location frontend
590
- $env:NODE_OPTIONS = "--max-old-space-size=8192"
591
- $lockFile = Join-Path (Get-Location) ".next\lock"
592
- if (Test-Path $lockFile) { Remove-Item -Force $lockFile -ErrorAction SilentlyContinue }
593
-
594
- Invoke-ModernCommand "Frontend Build" { npm run build }
595
- Invoke-ModernCommand "Frontend Vercel Deploy" { npx vercel deploy --prod }
596
- } finally {
597
- Pop-Location
598
- }
599
- } else {
600
- Add-Step "Frontend Deployment" "SKIPPED"
601
- }
602
-
603
- $finalReport = Generate-FinalReport "SUCCESS"
604
- Write-Host "\`nDEPLOYMENT SUCCESSFUL!" -ForegroundColor Green
605
- Write-Host $finalReport
606
- Write-Host "[CLIPBOARD] Full report copied to your clipboard!" -ForegroundColor Cyan
607
- `);
608
-
609
- // 3. Agent Rules
610
- writeFile('AGENTS.md', `
611
- # High-Performance Engineering Standards
612
- 1. **Ponytail, lazy senior dev mode**: The best code is the code never written. YAGNI. Question complex requests, reuse existing standards, prefer deletion over addition, and write the minimal correct code.
613
- 2. **Edge-First Execution**: Backend logic must be optimized for serverless edge deployment (Cloudflare Workers).
614
- 3. **Data Layer Optimization**: Never execute untuned queries. Use connection pooling and batching.
615
- 4. **Minimalist Frontend Render**: Ensure frontend implementations are highly optimized for Next.js, favoring SSR where appropriate, avoiding heavy client bundles, and ensuring mobile-first responsive design.
616
- 5. **Master Debugging**: Trace issues to their root cause before writing a fix. Use the "Five Whys" protocol.
617
- 6. **Strict Typing**: TypeScript and Rust types must be explicit, avoid \`any\` or \`unwrap()\` where possible.
618
- 7. **Structured Logging**: Implement structured logging, avoiding excessive console.logs in production.
619
- 8. **Atomic Commits**: Ensure commits are small, atomic, and pass CI checks before pushing.
620
- 9. **Error Boundaries**: Frontend must wrap critical components in Error Boundaries to prevent full app crashes.
621
- 10. **No Placeholder Code**: Never write generic "TODO" or placeholder logic if the requirements are known.
622
- 11. **Security Best Practices**: Enforce strict input validation, rate limiting, and secure credential handling by default.
623
- 12. **Automated Testing & TDD**: Require unit and integration tests for critical paths. Code is not done until it's tested.
624
- 13. **Strict Linting & Formatting**: Enforce ESLint, Prettier, and Clippy with zero warnings allowed.
625
- 14. **Accessibility (a11y) First**: Require ARIA labels, semantic HTML, and keyboard navigation for all frontend components.
626
- 15. **Documentation Standards**: Mandate JSDoc (frontend) and Rustdoc (backend) for all public APIs and reusable components.
627
- 16. **CI/CD Pipeline Setup**: Ensure GitHub Actions (or similar) are configured for automated checks, tests, and deployments.
628
- 17. **State Management Rules**: Prefer local state or React Context; enforce strict rules against unnecessary global state.
629
- 18. **Environment Variable Validation**: Fail fast at startup if required \`.env\` variables are missing.
630
- 19. **Kill AI Slop**: Remove aesthetic clichΓ©s (indigo gradients, glowing cards, excessive emojis) that serve no functional purpose.
631
- 20. **Security First Principle**: Treat all external input as malicious; leverage the Security Review skill for all external-facing endpoints.
632
-
633
- # Start Project Protocol
634
- When the user says "start project", you MUST immediately:
635
- 1. Ask the user for the name of the project and a brief description of what they want to build.
636
- 2. Wait for their response.
637
- 3. Once they respond, use their details to initialize the Next.js frontend (in \`frontend/\`) and Rust backend (in \`backend/\`), adhering to all High-Performance Engineering Standards.
638
- 4. Initialize a new git repository in the root directory (\`git init\`) and create an initial commit.
639
- `);
640
-
641
- // 4. Workflows
642
- writeFile('.agents/workflows/git-it.md', `
643
- # Safe Git Sync Workflow
644
- 1. Stage all changes (\`git add .\`).
645
- 2. Write a descriptive, conventional commit message based on the recent changes.
646
- 3. Commit the changes (\`git commit -m "..."\`).
647
- 4. Pull with rebase (\`git pull --rebase\`).
648
- 5. Push to the remote branch (\`git push\`).
649
- `);
650
-
651
- writeFile('.agents/workflows/deploy-and-talk.md', `
652
- # Deploy and Talk Workflow
653
- 1. Execute the deployment script (\`./deploy.ps1\` or \`./deploy.sh\`).
654
- 2. Verify successful deployment output.
655
- 3. Send a Telegram notification using \`Invoke-RestMethod\` to alert the team that the deployment is complete. Include a brief changelog.
656
- `);
657
-
658
- // 5. Skills
659
- writeFile('.agents/skills/ponytail/SKILL.md', `
660
- ---
661
- name: ponytail
662
- description: Enforces lazy senior dev principles. Efficient, not careless. Deletion over addition.
663
- ---
664
- # Ponytail, lazy senior dev mode
665
-
666
- You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
667
-
668
- Before writing any code, stop at the first rung that holds:
669
- 1. Does this need to be built at all? (YAGNI)
670
- 2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
671
- 3. Does the standard library already do this? Use it.
672
- 4. Does a native platform feature cover it? Use it.
673
- 5. Does an already-installed dependency solve it? Use it.
674
- 6. Can this be one line? Make it one line.
675
- 7. Only then: write the minimum code that works.
676
-
677
- The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
678
-
679
- Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once.
680
-
681
- Rules:
682
- - No abstractions that weren't explicitly requested.
683
- - No new dependency if it can be avoided.
684
- - No boilerplate nobody asked for.
685
- - Deletion over addition. Boring over clever. Fewest files possible.
686
- - Shortest working diff wins, but only once you understand the problem.
687
- - Question complex requests: "Do you actually need X, or does Y cover it?"
688
- - Mark deliberate simplifications that cut a real corner with a known ceiling with a \`ponytail:\` comment naming the ceiling and upgrade path.
689
- `);
690
-
691
- writeFile('.agents/skills/ui-sniper-telegram/SKILL.md', `
692
- ---
693
- name: ui-sniper-telegram
694
- description: Automates the setup of a UI Sniper system integrated with a Telegram bot for capturing frontend UI feedback directly to a Telegram group.
695
- ---
696
- # UI Sniper Telegram Integration Skill
697
- 1. **Ask for Credentials**: Prompt the user for their \`TELEGRAM_BOT_TOKEN\` and \`TELEGRAM_CHAT_ID\`. Wait for their response.
698
- 2. **Install**: Install \`ui-sniper\` and scaffold the Next.js API route (\`app/api/ui-sniper-webhook/route.ts\`), and set up the \`UISniperWrapper.tsx\` component.
699
- 3. **Configure**: Add the wrapper to \`app/layout.tsx\` and place the credentials in \`.env.local\`.
700
- `);
701
-
702
- writeFile('.agents/skills/kill-ai-slop/SKILL.md', `
703
- ---
704
- name: kill-ai-slop
705
- description: Scans a web project to detect and strip out AI-generated aesthetic clichΓ©s (indigo gradients, excessive emojis, glowing cards) replacing them with clean, tasteful, and accessible design patterns.
706
- ---
707
-
708
- # Kill AI Slop Skill
709
- Run the following command in the terminal to execute the scanner:
710
- \`\`\`bash
711
- npx --yes skills add yetone/kill-ai-slop
712
- \`\`\`
713
- Or execute the scanner directly on the project directory:
714
- \`\`\`bash
715
- npx --yes kill-ai-slop .
716
- \`\`\`
717
- Review the reported "tells" and apply the suggested clean fixes (e.g., removing indigo gradients, replacing emojis with proper SVG icons, removing glowing cards in favor of paper/ink aesthetics).
718
- `);
719
-
720
- writeFile('.agents/skills/security-review/SKILL.md', `
721
- ---
722
- name: security-review
723
- description: 'AI-powered codebase security scanner that reasons about code like a security researcher β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.'
724
- ---
725
-
726
- # Security Review
727
-
728
- An AI-powered security scanner that reasons about your codebase the way a human security researcher would β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.
729
-
730
- ## Execution Workflow
731
- 1. **Scope Resolution**: Determine what to scan (specific path or entire project).
732
- 2. **Dependency Audit**: Audit dependencies first (check package.json, Cargo.toml for known vulnerable packages).
733
- 3. **Secrets & Exposure Scan**: Scan ALL files for hardcoded API keys, tokens, passwords, and \`.env\` files.
734
- 4. **Vulnerability Deep Scan**: Check for Injection Flaws, Authentication/Access Control issues, Data Handling weaknesses, and Cryptography issues.
735
- 5. **Cross-File Data Flow Analysis**: Trace user-controlled input from entry points to dangerous sinks.
736
- 6. **Self-Verification Pass**: Filter false positives.
737
- 7. **Generate Security Report**: Output a findings summary table and structured report with severities (CRITICAL, HIGH, MEDIUM, LOW, INFO).
738
- 8. **Propose Patches**: For every CRITICAL and HIGH finding, generate a concrete patch for human review. **Never auto-apply any patch.**
739
- `);
740
-
741
- console.log('βœ… All skills, workflows, dev scripts, and rules generated successfully.');
742
- console.log('');
743
- console.log('πŸ“¦ Installing additional NPM packages in the current project...');
744
-
745
- try {
746
- // If there is a package.json, install UI dependencies
747
- if (fs.existsSync('package.json')) {
748
- console.log(' - Installing ui-sniper and html2canvas...');
749
- execSync('npm install ui-sniper html2canvas', { stdio: 'inherit' });
750
- } else {
751
- console.log(' - No package.json found. Skipping ui-sniper installation.');
752
- }
753
- } catch (error) {
754
- console.warn('⚠️ Could not install npm dependencies automatically.');
755
- }
756
-
757
- console.log('');
758
- console.log("\nπŸŽ‰ Scaffolding Complete! πŸŽ‰");
759
- console.log("πŸ‘‰ The workspace is fully loaded with Ponytail, Security Review, and Kill AI Slop skills.");
760
- console.log("πŸ‘‰ Go to your coding agent and say: 'start project' to begin! πŸš€");
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+
7
+ const pkg = require(path.join(__dirname, '../package.json'));
8
+ console.log(`πŸš€ Scaffold Project Environment (Hara-XY) v${pkg.version} πŸš€`);
9
+
10
+ function ensureDir(dir) {
11
+ if (!fs.existsSync(dir)) {
12
+ fs.mkdirSync(dir, { recursive: true });
13
+ }
14
+ }
15
+
16
+ function writeFile(filePath, content) {
17
+ const fullPath = path.resolve(process.cwd(), filePath);
18
+ ensureDir(path.dirname(fullPath));
19
+ fs.writeFileSync(fullPath, content.trim() + '\n', 'utf8');
20
+ console.log(`βœ… Created: ${filePath}`);
21
+ }
22
+
23
+ // 1. Dev Scripts
24
+ writeFile('dev.sh', `
25
+ #!/usr/bin/env bash
26
+ # =============================================
27
+ # Medizys Local Development Runner
28
+ # =============================================
29
+ # Starts the Worker and Next.js frontend
30
+ # using concurrently in a single terminal window.
31
+
32
+ echo ""
33
+ echo -e "\\e[36m=============================================\\e[0m"
34
+ echo -e "\\e[36m πŸš€ Medizys Local Dev Server\\e[0m"
35
+ echo -e "\\e[36m=============================================\\e[0m"
36
+ echo ""
37
+ echo -e "\\e[32m Backend β†’ http://localhost:8787\\e[0m"
38
+ echo -e "\\e[34m Frontend β†’ http://localhost:3000\\e[0m"
39
+ echo ""
40
+ echo -e "\\e[33m Starting unified development environment...\\e[0m"
41
+ echo -e "\\e[36m=============================================\\e[0m"
42
+ echo ""
43
+
44
+ # Ensure Node 22 is used if nvm is available
45
+ if [ -s "$HOME/.nvm/nvm.sh" ]; then
46
+ export NVM_DIR="$HOME/.nvm"
47
+ source "$NVM_DIR/nvm.sh"
48
+ nvm use 22 > /dev/null 2>&1
49
+ fi
50
+
51
+ # Cleanup zombie worker processes and ports to prevent conflicts
52
+ echo -e "\\e[90m Performing aggressive process cleanup...\\e[0m"
53
+
54
+ # Define processes to kill
55
+ for procName in workerd node concurrently; do
56
+ if pgrep -x "$procName" > /dev/null; then
57
+ echo -e "\\e[33m Stopping $procName...\\e[0m"
58
+ pkill -f "$procName" 2>/dev/null
59
+ fi
60
+ done
61
+
62
+ # Active verification loop to ensure all target processes have fully exited
63
+ maxWaitSeconds=5
64
+ start=$(date +%s)
65
+ while [ $(( $(date +%s) - start )) -lt $maxWaitSeconds ]; do
66
+ if ! pgrep -f "workerd|concurrently" > /dev/null; then
67
+ break
68
+ fi
69
+ sleep 0.2
70
+ done
71
+
72
+ # Function to robustly remove a directory with retry logic
73
+ remove_cache_directory() {
74
+ local path=$1
75
+ local label=$2
76
+ if [ -d "$path" ]; then
77
+ echo -e "\\e[90m Purging $label cache ($path)...\\e[0m"
78
+ rm -rf "$path"
79
+ fi
80
+ }
81
+
82
+ # Hard clear of wrangler state and Next.js cache
83
+ remove_cache_directory "worker/.wrangler" "wrangler state"
84
+ remove_cache_directory "frontend/.next" "frontend Turbopack cache"
85
+
86
+ for port in 8787 3000; do
87
+ echo -e "\\e[90m Ensuring port $port is free...\\e[0m"
88
+ retryCount=0
89
+ while [ $retryCount -lt 5 ]; do
90
+ targetPid=$(lsof -ti :$port)
91
+ if [ ! -z "$targetPid" ]; then
92
+ echo -e "\\e[31m Port $port occupied by PID $targetPid. Terminating...\\e[0m"
93
+ kill -9 $targetPid 2>/dev/null
94
+ sleep 1
95
+ retryCount=$((retryCount+1))
96
+ else
97
+ break
98
+ fi
99
+ done
100
+ done
101
+
102
+ # Disable wrangler usage metrics prompt
103
+ export WRANGLER_SEND_METRICS="false"
104
+ # Limit Node.js memory
105
+ export NODE_OPTIONS="--max-old-space-size=4096"
106
+
107
+ echo -e "\\e[32mStarting development environment...\\e[0m"
108
+
109
+ runnerPath="/tmp/mediqueue-dev-runner.js"
110
+
111
+ cat << 'EOF' > "$runnerPath"
112
+ const { spawn, execSync } = require('child_process');
113
+ let buffer = '';
114
+ let workerChild;
115
+ let frontendChild;
116
+ let lastRestartTime = 0;
117
+ const startTime = Date.now();
118
+
119
+ function formatSize(bytes) {
120
+ if (bytes < 1024) return bytes + 'B';
121
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
122
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
123
+ }
124
+
125
+ function formatUptime() {
126
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
127
+ const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
128
+ const s = (elapsed % 60).toString().padStart(2, '0');
129
+ const h = Math.floor(elapsed / 3600);
130
+ if (h > 0) return \`\${h}h \${m}m \${s}s\`;
131
+ return \`\${m}m \${s}s\`;
132
+ }
133
+
134
+ let lastRows = 0;
135
+ let lastCols = 0;
136
+
137
+ function updateLegend() {
138
+ if (!process.stdout.isTTY) return;
139
+ const rows = process.stdout.rows || 30;
140
+ const cols = process.stdout.columns || 80;
141
+
142
+ // Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
143
+ if (rows !== lastRows || cols !== lastCols) {
144
+ process.stdout.write(\`\\x1b[1;\${rows - 2}r\`);
145
+ lastRows = rows;
146
+ lastCols = cols;
147
+ }
148
+
149
+ const lineCount = (buffer.match(/\\n/g) || []).length;
150
+ const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
151
+ const uptimeStr = formatUptime();
152
+ const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
153
+ const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit \`;
154
+
155
+ // Draw legend
156
+ process.stdout.write(\`\\x1b[s\`); // save cursor
157
+ process.stdout.write(\`\\x1b[\${rows - 1};1H\\x1b[42m\\x1b[30m\${legend1.padEnd(cols)}\\x1b[0m\`);
158
+ process.stdout.write(\`\\x1b[\${rows};1H\\x1b[42m\\x1b[30m\${legend2.padEnd(cols)}\\x1b[0m\`);
159
+ process.stdout.write(\`\\x1b[u\`); // restore cursor
160
+ }
161
+
162
+ let legendInterval;
163
+ function startLegendUpdater() {
164
+ if (legendInterval) clearInterval(legendInterval);
165
+ legendInterval = setInterval(() => {
166
+ updateLegend();
167
+ }, 1000);
168
+ }
169
+
170
+ function cleanupLegend() {
171
+ if (legendInterval) clearInterval(legendInterval);
172
+ if (!process.stdout.isTTY) return;
173
+ process.stdout.write('\\x1b[r'); // reset scrolling region
174
+ }
175
+
176
+ process.stdout.on('resize', () => {
177
+ if (!process.stdout.isTTY) return;
178
+ cleanupLegend();
179
+ console.clear();
180
+ lastRows = 0;
181
+ updateLegend();
182
+ });
183
+
184
+ function extractErrors(text) {
185
+ const lines = text.split('\\n');
186
+ const errorPattern = /(error|exception|fail|β¨―|βœ—|traceback)/i;
187
+ let inErrorBlock = false;
188
+ let blockStart = 0;
189
+ const blocks = [];
190
+
191
+ for (let i = 0; i < lines.length; i++) {
192
+ if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
193
+ if (!inErrorBlock) {
194
+ inErrorBlock = true;
195
+ blockStart = Math.max(0, i - 2);
196
+ }
197
+ } else {
198
+ if (inErrorBlock) {
199
+ if (lines[i].trim() === '') continue;
200
+ const blockEnd = Math.min(lines.length - 1, i + 2);
201
+ blocks.push(lines.slice(blockStart, blockEnd + 1).join('\\n'));
202
+ inErrorBlock = false;
203
+ }
204
+ }
205
+ }
206
+ if (inErrorBlock) {
207
+ blocks.push(lines.slice(blockStart).join('\\n'));
208
+ }
209
+
210
+ return blocks.length > 0 ? blocks.join('\\n\\n--- [Next Error Block] ---\\n\\n') : '';
211
+ }
212
+
213
+ function formatPrefix(isError, source) {
214
+ const d = new Date();
215
+ const ts = String(d.getHours()).padStart(2, '0') + ':' +
216
+ String(d.getMinutes()).padStart(2, '0') + ':' +
217
+ String(d.getSeconds()).padStart(2, '0');
218
+
219
+ const color = source === 'worker' ? '\\x1b[36m' : '\\x1b[35m';
220
+ const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
221
+ return \`\\x1b[90m[\${ts}]\\x1b[0m \${color}\${tag}\\x1b[0m \`;
222
+ }
223
+
224
+ function createStreamHandler(isError, source) {
225
+ let remainder = '';
226
+ return (d) => {
227
+ const chunk = remainder + d.toString();
228
+ const lines = chunk.split('\\n');
229
+ remainder = lines.pop();
230
+ for (let line of lines) {
231
+ line = line.replace(/\\x1b\\[[23]J/g, '').replace(/\\x1b\\[[0-9;]*H/g, '').replace(/\\x1bc/g, '');
232
+ line = line.replace(/\\x1b\\[[0-9]*[A-G]/g, '').replace(/\\x1b\\[[0-9]*[K]/g, '');
233
+ line = line.replace(/\\r/g, '');
234
+ line = line.replace(/^\\[[^\\]]+\\]\\s*/, '');
235
+
236
+ if (line.trim() === '') continue;
237
+
238
+ const prefix = formatPrefix(isError, source);
239
+ const out = prefix + line + '\\n';
240
+
241
+ if (isError) process.stderr.write(out);
242
+ else process.stdout.write(out);
243
+ buffer += out;
244
+ }
245
+ if (buffer.length > 1000000) {
246
+ const idx = buffer.indexOf('\\n', buffer.length - 500000);
247
+ buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
248
+ }
249
+ };
250
+ }
251
+
252
+ function startWorker() {
253
+ workerChild = spawn('npx', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
254
+ env: { ...process.env, FORCE_COLOR: '1' },
255
+ stdio: ['ignore', 'pipe', 'pipe'],
256
+ shell: true
257
+ });
258
+ workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
259
+ workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
260
+ }
261
+
262
+ function startFrontend() {
263
+ frontendChild = spawn('npx', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
264
+ env: { ...process.env, FORCE_COLOR: '1' },
265
+ stdio: ['ignore', 'pipe', 'pipe'],
266
+ shell: true
267
+ });
268
+ frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
269
+ frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
270
+ }
271
+
272
+ function startServices() {
273
+ startWorker();
274
+ startFrontend();
275
+ }
276
+
277
+ console.clear();
278
+ updateLegend();
279
+ startLegendUpdater();
280
+ startServices();
281
+
282
+ function copyToClipboard(text) {
283
+ const platform = process.platform;
284
+ let cmd = '';
285
+ if (platform === 'darwin') cmd = 'pbcopy';
286
+ else if (platform === 'win32') cmd = 'clip';
287
+ else cmd = 'xclip -selection clipboard';
288
+ try {
289
+ execSync(cmd, { input: text });
290
+ return true;
291
+ } catch (e) {
292
+ return false;
293
+ }
294
+ }
295
+
296
+ if (process.stdin.isTTY) {
297
+ process.stdin.setRawMode(true);
298
+ process.stdin.resume();
299
+ process.stdin.setEncoding('utf8');
300
+ process.stdin.on('data', k => {
301
+ const now = Date.now();
302
+ if (k === 'c' || k === 'C') {
303
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
304
+ if (!clean.trim()) {
305
+ console.log('\\n\\x1b[33m⚠️ Buffer is empty.\\x1b[0m\\n');
306
+ return;
307
+ }
308
+ if (copyToClipboard(clean)) console.log('\\n\\x1b[32mπŸ“‹ Copied ALL logs to clipboard!\\x1b[0m\\n');
309
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy. Ensure xclip/pbcopy is installed.\\x1b[0m\\n');
310
+ } else if (k === 's' || k === 'S') {
311
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
312
+ const shortLines = clean.split('\\n').slice(-500).join('\\n');
313
+ if (copyToClipboard(shortLines)) console.log('\\n\\x1b[32mπŸ“‹ Copied last 500 lines to clipboard!\\x1b[0m\\n');
314
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy.\\x1b[0m\\n');
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
+ if (copyToClipboard(workerLogs)) console.log('\\n\\x1b[32mπŸ“‹ Copied BACKEND logs to clipboard!\\x1b[0m\\n');
319
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy.\\x1b[0m\\n');
320
+ } else if (k === 'f' || k === 'F') {
321
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
322
+ const frontendLogs = clean.split('\\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\\n');
323
+ if (copyToClipboard(frontendLogs)) console.log('\\n\\x1b[32mπŸ“‹ Copied FRONTEND logs to clipboard!\\x1b[0m\\n');
324
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy.\\x1b[0m\\n');
325
+ } else if (k === 'e' || k === 'E') {
326
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
327
+ const errors = extractErrors(clean);
328
+ if (errors) {
329
+ if (copyToClipboard(errors)) console.log('\\n\\x1b[31m🚨 Errors copied to clipboard!\\x1b[0m\\n');
330
+ else console.log('\\n\\x1b[31m⚠️ Failed to copy errors.\\x1b[0m\\n');
331
+ } else {
332
+ console.log('\\n\\x1b[32mβœ… No errors detected.\\x1b[0m\\n');
333
+ }
334
+ } else if (k === 'r' || k === 'R') {
335
+ if (now - lastRestartTime < 1000) return;
336
+ lastRestartTime = now;
337
+ console.log('\\n\\x1b[33mπŸ”„ Restarting ALL services...\\x1b[0m\\n');
338
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
339
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
340
+ startServices();
341
+ } else if (k === '1') {
342
+ if (now - lastRestartTime < 1000) return;
343
+ lastRestartTime = now;
344
+ console.log('\\n\\x1b[33mπŸ”„ Restarting FRONTEND...\\x1b[0m\\n');
345
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
346
+ startFrontend();
347
+ } else if (k === '2') {
348
+ if (now - lastRestartTime < 1000) return;
349
+ lastRestartTime = now;
350
+ console.log('\\n\\x1b[33mπŸ”„ Restarting BACKEND...\\x1b[0m\\n');
351
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
352
+ startWorker();
353
+ } else if (k === 'x' || k === 'X') {
354
+ console.clear();
355
+ buffer = '';
356
+ lastRows = 0;
357
+ updateLegend();
358
+ console.log('\\x1b[32m🧹 Terminal cleared.\\x1b[0m\\n');
359
+ } else if (k === '\\u0003' || k === 'q' || k === 'Q') {
360
+ console.log('\\n\\x1b[33mStopping services...\\x1b[0m');
361
+ cleanupLegend();
362
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
363
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
364
+ process.exit();
365
+ }
366
+ });
367
+ }
368
+
369
+ // Cleanup on unexpected exits
370
+ process.on('SIGINT', () => {
371
+ cleanupLegend();
372
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
373
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
374
+ process.exit();
375
+ });
376
+ process.on('exit', () => {
377
+ cleanupLegend();
378
+ if (workerChild) try { process.kill(workerChild.pid, 'SIGKILL'); } catch(e) {}
379
+ if (frontendChild) try { process.kill(frontendChild.pid, 'SIGKILL'); } catch(e) {}
380
+ });
381
+ EOF
382
+
383
+ node "$runnerPath"
384
+
385
+ `);
386
+ fs.chmodSync(path.resolve(process.cwd(), 'dev.sh'), '755');
387
+
388
+ writeFile('dev.ps1', `
389
+ # =============================================
390
+ # Medizys Local Development Runner
391
+ # =============================================
392
+ # Starts the Worker and Next.js frontend
393
+ # using concurrently in a single terminal window.
394
+
395
+ Write-Host ""
396
+ Write-Host "=============================================" -ForegroundColor Cyan
397
+ Write-Host " πŸš€ Medizys Local Dev Server" -ForegroundColor Cyan
398
+ Write-Host "=============================================" -ForegroundColor Cyan
399
+ Write-Host ""
400
+ Write-Host " Backend β†’ http://localhost:8787" -ForegroundColor Green
401
+ Write-Host " Frontend β†’ http://localhost:3000" -ForegroundColor Blue
402
+ Write-Host ""
403
+ Write-Host " Starting unified development environment..." -ForegroundColor Yellow
404
+ Write-Host "=============================================" -ForegroundColor Cyan
405
+ Write-Host ""
406
+
407
+ # Cleanup zombie worker processes and ports to prevent conflicts
408
+ Write-Host " Performing aggressive process cleanup..." -ForegroundColor Gray
409
+
410
+ # Define processes to kill
411
+ $procNames = @("workerd", "node", "concurrently")
412
+
413
+ foreach ($procName in $procNames) {
414
+ $procs = Get-Process $procName -ErrorAction SilentlyContinue
415
+ if ($procs) {
416
+ Write-Host " Stopping $procName ($($procs.Count) instances)..." -ForegroundColor Yellow
417
+ $procs | Stop-Process -Force -ErrorAction SilentlyContinue
418
+ }
419
+ }
420
+
421
+ # Active verification loop to ensure all target processes have fully exited
422
+ $maxWaitSeconds = 5
423
+ $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
424
+ while ($stopwatch.Elapsed.TotalSeconds -lt $maxWaitSeconds) {
425
+ $remainingProcs = Get-Process $procNames -ErrorAction SilentlyContinue
426
+ if (-not $remainingProcs) {
427
+ break
428
+ }
429
+ Start-Sleep -Milliseconds 200
430
+ }
431
+
432
+ # Function to robustly remove a directory with retry logic
433
+ function Remove-CacheDirectory($path, $label) {
434
+ if (Test-Path $path) {
435
+ Write-Host " Purging $label cache ($path)..." -ForegroundColor Gray
436
+ $attempts = 0
437
+ $maxAttempts = 5
438
+ $success = $false
439
+
440
+ while ($attempts -lt $maxAttempts -and -not $success) {
441
+ try {
442
+ Remove-Item -Recurse -Force $path -ErrorAction Stop
443
+ $success = $true
444
+ } catch {
445
+ $attempts++
446
+ if ($attempts -lt $maxAttempts) {
447
+ Write-Host " Lock detected on $path. Retrying ($attempts/$maxAttempts)..." -ForegroundColor DarkYellow
448
+ Start-Sleep -Milliseconds (300 * $attempts)
449
+ } else {
450
+ Write-Host " Warning: Could not fully purge $path. Some files may be locked." -ForegroundColor Red
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+
457
+ # Hard clear of wrangler state and Next.js cache to prevent Turbopack panics and corrupted dependency graphs
458
+ Remove-CacheDirectory "worker/.wrangler" "wrangler state"
459
+ Remove-CacheDirectory "frontend/.next" "frontend Turbopack cache"
460
+
461
+ $ports = @(8787, 3000)
462
+ foreach ($port in $ports) {
463
+ Write-Host " Ensuring port $port is free..." -ForegroundColor Gray
464
+ $retryCount = 0
465
+ while ($retryCount -lt 5) {
466
+ $netstatOutput = netstat -ano | Select-String ":$port\\s+.*LISTENING\\s+(\\d+)"
467
+ if ($netstatOutput) {
468
+ $targetPid = $netstatOutput.Matches[0].Groups[1].Value
469
+ Write-Host " Port $port occupied by PID $targetPid. Terminating..." -ForegroundColor Red
470
+ Stop-Process -Id $targetPid -Force -ErrorAction SilentlyContinue
471
+ Start-Sleep -Seconds 1
472
+ $retryCount++
473
+ } else {
474
+ break
475
+ }
476
+ }
477
+ }
478
+
479
+ # Telegram watcher disabled by user request
480
+ # Write-Host " Starting Telegram watcher in background..." -ForegroundColor Magenta
481
+ # $watcherJob = Start-Job -ScriptBlock {
482
+ # & "$using:PSScriptRoot\\scripts\\telegram-watcher.ps1"
483
+ # }
484
+ # Write-Host " Telegram watcher running (Job ID: $($watcherJob.Id))" -ForegroundColor Magenta
485
+ # Write-Host " Reply to @mediisysbot on Telegram to queue instructions." -ForegroundColor DarkMagenta
486
+ # Write-Host ""
487
+
488
+ # Disable wrangler usage metrics prompt to prevent concurrently from hanging
489
+ $env:WRANGLER_SEND_METRICS = "false"
490
+
491
+ # Limit Node.js memory to prevent OOM crashes while providing 4GB for Next.js App Router
492
+ $env:NODE_OPTIONS = "--max-old-space-size=4096"
493
+
494
+ # Run the monorepo dev command through a Node.js wrapper to capture logs
495
+ # and listen for the 'c' keypress to copy output to the clipboard.
496
+ Write-Host "Starting development environment..." -ForegroundColor Green
497
+ $runnerScript = @'
498
+ const { spawn, execSync } = require('child_process');
499
+ let buffer = '';
500
+ let workerChild;
501
+ let frontendChild;
502
+ let lastRestartTime = 0;
503
+ const startTime = Date.now();
504
+
505
+ function formatSize(bytes) {
506
+ if (bytes < 1024) return bytes + 'B';
507
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
508
+ return (bytes / (1024 * 1024)).toFixed(1) + 'MB';
509
+ }
510
+
511
+ function formatUptime() {
512
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
513
+ const m = Math.floor(elapsed / 60).toString().padStart(2, '0');
514
+ const s = (elapsed % 60).toString().padStart(2, '0');
515
+ const h = Math.floor(elapsed / 3600);
516
+ if (h > 0) return \`\${h}h \${m}m \${s}s\`;
517
+ return \`\${m}m \${s}s\`;
518
+ }
519
+
520
+ let lastRows = 0;
521
+ let lastCols = 0;
522
+
523
+ function updateLegend() {
524
+ if (!process.stdout.isTTY) return;
525
+ const rows = process.stdout.rows || 30;
526
+ const cols = process.stdout.columns || 80;
527
+
528
+ // Set scrolling region only if changed (DECSTBM homes the cursor to 1,1!)
529
+ if (rows !== lastRows || cols !== lastCols) {
530
+ process.stdout.write(\`\\x1b[1;\${rows - 2}r\`);
531
+ lastRows = rows;
532
+ lastCols = cols;
533
+ }
534
+
535
+ const lineCount = (buffer.match(/\\n/g) || []).length;
536
+ const sizeStr = formatSize(Buffer.byteLength(buffer, 'utf8'));
537
+ const uptimeStr = formatUptime();
538
+ const legend1 = \` [Copy] 'c': All (\${lineCount}L / \${sizeStr}) | 's': 500L | 'w': Backend | 'f': Frontend | 'e': Errors \`;
539
+ const legend2 = \` [System] Uptime: \${uptimeStr} | 'x': Clear | 'r': Restart ALL | '1': Frontend | '2': Backend | 'q': Quit | 'Q': Clean & Quit \`;
540
+
541
+ // Draw legend
542
+ process.stdout.write(\`\\x1b[s\`); // save cursor
543
+ process.stdout.write(\`\\x1b[\${rows - 1};1H\\x1b[42m\\x1b[30m\${legend1.padEnd(cols)}\\x1b[0m\`);
544
+ process.stdout.write(\`\\x1b[\${rows};1H\\x1b[42m\\x1b[30m\${legend2.padEnd(cols)}\\x1b[0m\`);
545
+ process.stdout.write(\`\\x1b[u\`); // restore cursor
546
+ }
547
+
548
+ let legendInterval;
549
+ function startLegendUpdater() {
550
+ if (legendInterval) clearInterval(legendInterval);
551
+ legendInterval = setInterval(() => {
552
+ updateLegend();
553
+ }, 1000);
554
+ }
555
+
556
+ function cleanupLegend() {
557
+ if (legendInterval) clearInterval(legendInterval);
558
+ if (!process.stdout.isTTY) return;
559
+ process.stdout.write('\\x1b[r'); // reset scrolling region
560
+ }
561
+
562
+ process.stdout.on('resize', () => {
563
+ if (!process.stdout.isTTY) return;
564
+ cleanupLegend();
565
+ console.clear();
566
+ lastRows = 0;
567
+ updateLegend();
568
+ });
569
+
570
+ function extractErrors(text) {
571
+ const lines = text.split('\\n');
572
+ const errorPattern = /(error|exception|fail|β¨―|βœ—|traceback)/i;
573
+ let inErrorBlock = false;
574
+ let blockStart = 0;
575
+ const blocks = [];
576
+
577
+ for (let i = 0; i < lines.length; i++) {
578
+ // Check for error keywords or stack trace lines
579
+ if (errorPattern.test(lines[i]) || (inErrorBlock && lines[i].trim().startsWith('at '))) {
580
+ if (!inErrorBlock) {
581
+ inErrorBlock = true;
582
+ blockStart = Math.max(0, i - 2); // 2 lines of context before
583
+ }
584
+ } else {
585
+ if (inErrorBlock) {
586
+ // If the next line is empty, we might just be between stack traces
587
+ if (lines[i].trim() === '') continue;
588
+
589
+ // End of error block
590
+ const blockEnd = Math.min(lines.length - 1, i + 2); // 2 lines of context after
591
+ blocks.push(lines.slice(blockStart, blockEnd + 1).join('\\n'));
592
+ inErrorBlock = false;
593
+ }
594
+ }
595
+ }
596
+ if (inErrorBlock) {
597
+ blocks.push(lines.slice(blockStart).join('\\n'));
598
+ }
599
+
600
+ return blocks.length > 0 ? blocks.join('\\n\\n--- [Next Error Block] ---\\n\\n') : '';
601
+ }
602
+
603
+ function formatPrefix(isError, source) {
604
+ const d = new Date();
605
+ const ts = String(d.getHours()).padStart(2, '0') + ':' +
606
+ String(d.getMinutes()).padStart(2, '0') + ':' +
607
+ String(d.getSeconds()).padStart(2, '0');
608
+
609
+ // Cyan for worker, Magenta for frontend
610
+ const color = source === 'worker' ? '\\x1b[36m' : '\\x1b[35m';
611
+ const tag = source === 'worker' ? '[BACKEND]' : '[FRONTEND]';
612
+ return \`\\x1b[90m[\${ts}]\\x1b[0m \${color}\${tag}\\x1b[0m \`;
613
+ }
614
+
615
+ function createStreamHandler(isError, source) {
616
+ let remainder = '';
617
+ return (d) => {
618
+ const chunk = remainder + d.toString();
619
+ const lines = chunk.split('\\n');
620
+ remainder = lines.pop();
621
+ for (let line of lines) {
622
+ // Strip terminal-clearing and cursor-resetting escape codes
623
+ line = line.replace(/\\x1b\\[[23]J/g, '').replace(/\\x1b\\[[0-9;]*H/g, '').replace(/\\x1bc/g, '');
624
+ // Strip cursor up/down and line clear codes
625
+ line = line.replace(/\\x1b\\[[0-9]*[A-G]/g, '').replace(/\\x1b\\[[0-9]*[K]/g, '');
626
+ // Strip carriage returns which cause overwrite from start of line
627
+ line = line.replace(/\\r/g, '');
628
+
629
+ // Remove concurrently's prefix so we can add our own clean one
630
+ line = line.replace(/^\\[[^\\]]+\\]\\s*/, '');
631
+
632
+ if (line.trim() === '') continue;
633
+
634
+ const prefix = formatPrefix(isError, source);
635
+ const out = prefix + line + '\\n';
636
+
637
+ if (isError) process.stderr.write(out);
638
+ else process.stdout.write(out);
639
+ buffer += out;
640
+ }
641
+ if (buffer.length > 1000000) {
642
+ const idx = buffer.indexOf('\\n', buffer.length - 500000);
643
+ buffer = buffer.slice(idx === -1 ? -500000 : idx + 1);
644
+ }
645
+ };
646
+ }
647
+
648
+ function startWorker() {
649
+ workerChild = spawn('npx.cmd', ['concurrently', '-c', 'cyan', '-n', 'worker', '"npm run dev --prefix worker"'], {
650
+ env: { ...process.env, FORCE_COLOR: '1' },
651
+ stdio: ['ignore', 'pipe', 'pipe'],
652
+ windowsHide: true,
653
+ shell: true
654
+ });
655
+ workerChild.stdout.on('data', createStreamHandler(false, 'worker'));
656
+ workerChild.stderr.on('data', createStreamHandler(true, 'worker'));
657
+ }
658
+
659
+ function startFrontend() {
660
+ frontendChild = spawn('npx.cmd', ['concurrently', '-c', 'magenta', '-n', 'frontend', '"npm run dev --prefix frontend"'], {
661
+ env: { ...process.env, FORCE_COLOR: '1' },
662
+ stdio: ['ignore', 'pipe', 'pipe'],
663
+ windowsHide: true,
664
+ shell: true
665
+ });
666
+ frontendChild.stdout.on('data', createStreamHandler(false, 'frontend'));
667
+ frontendChild.stderr.on('data', createStreamHandler(true, 'frontend'));
668
+ }
669
+
670
+ function startServices() {
671
+ startWorker();
672
+ startFrontend();
673
+ }
674
+
675
+ console.clear();
676
+ updateLegend();
677
+ startLegendUpdater();
678
+ startServices();
679
+
680
+ if (process.stdin.isTTY) {
681
+ process.stdin.setRawMode(true);
682
+ process.stdin.resume();
683
+ process.stdin.setEncoding('utf8');
684
+ process.stdin.on('data', k => {
685
+ const now = Date.now();
686
+ if (k === 'c' || k === 'C') {
687
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
688
+ if (!clean.trim()) {
689
+ console.log('\\n\\x1b[33m⚠️ Buffer is empty.\\x1b[0m\\n');
690
+ return;
691
+ }
692
+ try {
693
+ execSync('clip', { input: clean });
694
+ console.log('\\n\\x1b[32mπŸ“‹ Copied ALL logs to clipboard!\\x1b[0m\\n');
695
+ } catch (e) {}
696
+ } else if (k === 's' || k === 'S') {
697
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
698
+ const shortLines = clean.split('\\n').slice(-500).join('\\n');
699
+ try {
700
+ execSync('clip', { input: shortLines });
701
+ console.log('\\n\\x1b[32mπŸ“‹ Copied last 500 lines to clipboard!\\x1b[0m\\n');
702
+ } catch (e) {}
703
+ } else if (k === 'w' || k === 'W') {
704
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
705
+ const workerLogs = clean.split('\\n').filter(l => l.includes('[BACKEND]')).slice(-1000).join('\\n');
706
+ try {
707
+ execSync('clip', { input: workerLogs });
708
+ console.log('\\n\\x1b[32mπŸ“‹ Copied BACKEND logs to clipboard!\\x1b[0m\\n');
709
+ } catch (e) {}
710
+ } else if (k === 'f' || k === 'F') {
711
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
712
+ const frontendLogs = clean.split('\\n').filter(l => l.includes('[FRONTEND]')).slice(-1000).join('\\n');
713
+ try {
714
+ execSync('clip', { input: frontendLogs });
715
+ console.log('\\n\\x1b[32mπŸ“‹ Copied FRONTEND logs to clipboard!\\x1b[0m\\n');
716
+ } catch (e) {}
717
+ } else if (k === 'e' || k === 'E') {
718
+ const clean = buffer.replace(/\\x1B(?:[@-Z\\-_]|\\[[0-?]*[ -/]*[@-~])/g, '');
719
+ const errors = extractErrors(clean);
720
+ if (errors) {
721
+ try {
722
+ execSync('clip', { input: errors });
723
+ console.log('\\n\\x1b[31m🚨 Errors copied to clipboard!\\x1b[0m\\n');
724
+ } catch (e) {}
725
+ } else {
726
+ console.log('\\n\\x1b[32mβœ… No errors detected.\\x1b[0m\\n');
727
+ }
728
+ } else if (k === 'r' || k === 'R') {
729
+ if (now - lastRestartTime < 1000) return;
730
+ lastRestartTime = now;
731
+ console.log('\\n\\x1b[33mπŸ”„ Restarting ALL services...\\x1b[0m\\n');
732
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
733
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
734
+ startServices();
735
+ } else if (k === '1') {
736
+ if (now - lastRestartTime < 1000) return;
737
+ lastRestartTime = now;
738
+ console.log('\\n\\x1b[33mπŸ”„ Restarting FRONTEND...\\x1b[0m\\n');
739
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
740
+ startFrontend();
741
+ } else if (k === '2') {
742
+ if (now - lastRestartTime < 1000) return;
743
+ lastRestartTime = now;
744
+ console.log('\\n\\x1b[33mπŸ”„ Restarting BACKEND...\\x1b[0m\\n');
745
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
746
+ startWorker();
747
+ } else if (k === 'x' || k === 'X') {
748
+ console.clear();
749
+ buffer = '';
750
+ lastRows = 0;
751
+ updateLegend();
752
+ console.log('\\x1b[32m🧹 Terminal cleared.\\x1b[0m\\n');
753
+ } else if (k === '\\u0003' || k === 'q') {
754
+ console.log('\\n\\x1b[33mStopping services...\\x1b[0m');
755
+ cleanupLegend();
756
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
757
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
758
+ process.exit();
759
+ } else if (k === 'Q') {
760
+ console.log('\\n\\x1b[31m🧹 Cleaning cache and dependencies, then stopping services...\\x1b[0m');
761
+ cleanupLegend();
762
+ if (workerChild) try { execSync('taskkill /pid ' + workerChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
763
+ if (frontendChild) try { execSync('taskkill /pid ' + frontendChild.pid + ' /t /f', { stdio: 'ignore' }); } catch(e) {}
764
+
765
+ console.log('\\x1b[33mRemoving node_modules, .next, and .wrangler...\\x1b[0m');
766
+ const fs = require('fs');
767
+ const pathsToClean = ['frontend/.next', 'worker/.wrangler', 'frontend/node_modules', 'worker/node_modules', 'node_modules'];
768
+ for (const p of pathsToClean) {
769
+ try { fs.rmSync(p, { recursive: true, force: true }); } catch(e) {}
770
+ }
771
+ console.log('\\x1b[32mCleanup complete.\\x1b[0m');
772
+ process.exit();
773
+ }
774
+ });
775
+ }
776
+
777
+ // Cleanup on unexpected exits
778
+ process.on('SIGINT', () => {
779
+ cleanupLegend();
780
+ process.exit();
781
+ });
782
+ process.on('exit', () => {
783
+ cleanupLegend();
784
+ console.clear();
785
+ });
786
+ '@
787
+ $runnerPath = Join-Path $env:TEMP "mediqueue-dev-runner.js"
788
+ Set-Content -Path $runnerPath -Value $runnerScript -Encoding UTF8
789
+ node $runnerPath
790
+
791
+ # Cleanup watcher on exit
792
+ if ($null -ne (Get-Variable -Name watcherJob -ErrorAction SilentlyContinue)) {
793
+ if ($watcherJob) {
794
+ Stop-Job -Job $watcherJob -ErrorAction SilentlyContinue
795
+ Remove-Job -Job $watcherJob -ErrorAction SilentlyContinue
796
+ }
797
+ }
798
+
799
+ `);
800
+
801
+ // 2. Deploy Scripts
802
+ writeFile('deploy.sh', `
803
+ TARGET=$(readlink -f ~/mediqueue_linux/mediqueue/frontend/node_modules/@cloudflare/next-on-pages/dist/index.js)
804
+ sed -i 's/await (0, import_esbuild.build)({/await (0, import_esbuild.build)({ logLevel: "error",/g' $TARGET
805
+ sed -i 's/await (0, import_esbuild2.build)({/await (0, import_esbuild2.build)({ logLevel: "error",/g' $TARGET
806
+ cd ~/mediqueue_linux/mediqueue/frontend
807
+ export NODE_OPTIONS="--max_old_space_size=16384"
808
+ npx @cloudflare/next-on-pages
809
+ cp -r .vercel /mnt/c/Users/shivg/Desktop/Hara-XY/mediqueue-main/mediqueue/frontend/
810
+ cd /mnt/c/Users/shivg/Desktop/Hara-XY/mediqueue-main/mediqueue/frontend
811
+ $env:WRANGLER_SEND_METRICS="false"
812
+ npx wrangler pages deploy .vercel/output/static
813
+
814
+ `);
815
+ fs.chmodSync(path.resolve(process.cwd(), 'deploy.sh'), '755');
816
+
817
+ writeFile('deploy.ps1', `
818
+ param(
819
+ [switch]$SkipSentry = $false,
820
+ [switch]$SkipSeed = $false,
821
+ [switch]$SkipApp = $false,
822
+ [switch]$SkipRouter = $false,
823
+ [switch]$SkipFrontend = $false,
824
+ [switch]$SkipDBSync = $false,
825
+ [switch]$SkipVisual = $false,
826
+ [switch]$Fast = $false
827
+ )
828
+
829
+ if ($Fast) {
830
+ Write-Host "[FAST] Fast mode enabled. Skipping DB, Router, and App releases." -ForegroundColor Cyan
831
+ $SkipSeed = $true
832
+ $SkipApp = $true
833
+ $SkipRouter = $true
834
+ $SkipFrontend = $false
835
+ $SkipSentry = $true
836
+ $SkipDBSync = $true
837
+ $SkipVisual = $true
838
+ }
839
+
840
+ # Medizys Modernized Deployment Script
841
+ # Features: Schema Sync, Consolidated Reporting, Timestamped Logs, Clipboard Integration
842
+
843
+ $ErrorActionPreference = 'Stop'
844
+
845
+ # Ensure we are in the root of the project
846
+ Set-Location $PSScriptRoot
847
+
848
+ # Load .env and .env.local files if present
849
+ $envFiles = @(".env", ".env.local", "frontend\\.env.local")
850
+ foreach ($file in $envFiles) {
851
+ $envFile = Join-Path $PSScriptRoot $file
852
+ if (Test-Path $envFile) {
853
+ Write-Host "[RESILIENCE] Loading environment variables from $file..." -ForegroundColor Cyan
854
+ Get-Content $envFile | Where-Object { $_ -match '^\\s*([^#\\s][^=]+)=(.*)' } | ForEach-Object {
855
+ $name = $matches[1].Trim()
856
+ $value = $matches[2].Trim()
857
+ if ($value -match '^"(.*)"$') { $value = $matches[1] }
858
+ elseif ($value -match "^'(.*)'$") { $value = $matches[1] }
859
+ [Environment]::SetEnvironmentVariable($name, $value, "Process")
860
+ }
861
+ }
862
+ }
863
+
864
+ # PROACTIVE COLLISION PREVENTION: Kill competing deployment tasks to prevent file locks
865
+ Write-Host "[RESILIENCE] Terminating conflicting build processes to prevent locks..." -ForegroundColor Yellow
866
+ $conflictingProcs = Get-WmiObject Win32_Process | Where-Object {
867
+ $_.Name -match 'node.exe' -and
868
+ ($_.CommandLine -match 'next build' -or $_.CommandLine -match 'tsc' -or $_.CommandLine -match 'wrangler deploy')
869
+ }
870
+ foreach ($p in $conflictingProcs) {
871
+ Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
872
+ }
873
+
874
+ $GlobalReport = [PSCustomObject]@{
875
+ StartTime = (Get-Date)
876
+ Status = "Running"
877
+ Steps = @()
878
+ Error = $null
879
+ }
880
+
881
+ # Create reports directory in root
882
+ $ReportsDir = Join-Path $PSScriptRoot "deployment_reports"
883
+ if (!(Test-Path $ReportsDir)) { New-Item -ItemType Directory -Path $ReportsDir | Out-Null }
884
+
885
+ function Add-Step {
886
+ param([string]$Name, [string]$Status, [string]$Details = "")
887
+ $GlobalReport.Steps += [PSCustomObject]@{
888
+ Name = $Name
889
+ Status = $Status
890
+ Details = $Details
891
+ Time = (Get-Date).ToString("T")
892
+ }
893
+ }
894
+
895
+ function Generate-FinalReport {
896
+ param([string]$FinalStatus)
897
+ $GlobalReport.Status = $FinalStatus
898
+
899
+ # Get IP and IST Time
900
+ $ipStr = "Unknown"
901
+ try { $ipStr = (Invoke-RestMethod -Uri 'https://api.ipify.org' -TimeoutSec 3).Trim() } catch { }
902
+
903
+ $istTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById("India Standard Time")
904
+ $istStartTime = [System.TimeZoneInfo]::ConvertTimeFromUtc($GlobalReport.StartTime.ToUniversalTime(), $istTimeZone)
905
+
906
+ $reportText = "=================================================\`n"
907
+ $reportText += "MEDIQUEUE DEPLOYMENT REPORT\`n"
908
+ $reportText += "=================================================\`n"
909
+ $reportText += "Status: $FinalStatus\`n"
910
+ $reportText += "IP: $ipStr\`n"
911
+ $reportText += "Timestamp: $($istStartTime.ToString('MM/dd/yyyy HH:mm:ss')) (IST)\`n"
912
+ $reportText += "Duration: $((Get-Date) - $GlobalReport.StartTime)\`n"
913
+ $reportText += "-------------------------------------------------\`n"
914
+
915
+ foreach ($step in $GlobalReport.Steps) {
916
+ $indicator = if ($step.Status -eq "SUCCESS") { "[OK]" } else { "[FAIL]" }
917
+ $reportText += "$indicator [$($step.Time)] $($step.Name): $($step.Status)\`n"
918
+ if ($step.Details) { $reportText += " > $($step.Details)\`n" }
919
+ }
920
+
921
+ if ($GlobalReport.Error) {
922
+ $reportText += "-------------------------------------------------\`n"
923
+ $reportText += "CRITICAL ERROR:\`n$($GlobalReport.Error)\`n"
924
+ }
925
+ $reportText += "=================================================\`n"
926
+
927
+ # Save to file
928
+ $statusIndicator = if ($FinalStatus -eq "SUCCESS") { "SUCCESS" } else { "FAILED" }
929
+ $hour = (Get-Date).Hour
930
+ $timeOfDay = if ($hour -ge 5 -and $hour -lt 12) { "Morning" }
931
+ elseif ($hour -ge 12 -and $hour -lt 17) { "Afternoon" }
932
+ elseif ($hour -ge 17 -and $hour -lt 21) { "Evening" }
933
+ else { "Night" }
934
+
935
+ $humanTime = (Get-Date).ToString("ddMMM_hh-mmtt") # e.g. 30Mar_02-30PM
936
+ $fileName = "Deploy_\${statusIndicator}_\${timeOfDay}_$humanTime.txt"
937
+ $filePath = Join-Path $ReportsDir $fileName
938
+ $reportText | Out-File -FilePath $filePath
939
+
940
+ # Copy to clipboard
941
+ try { $reportText | Set-Clipboard } catch {}
942
+
943
+ return $reportText
944
+ }
945
+
946
+ function Send-Notification {
947
+ param([string]$Status, [string]$Report)
948
+ try {
949
+ Write-Host "[NOTIF] Dispatching deployment pulse..." -ForegroundColor Magenta
950
+ $notifPayload = (@{
951
+ version = "v2.5.2-stable"
952
+ env = "production"
953
+ status = $Status
954
+ report = $Report
955
+ chatId = "-1003863805343"
956
+ } | ConvertTo-Json)
957
+
958
+ Invoke-RestMethod -Uri "https://api.mediqueue.hara-xy.com/api/platform/notif/deploy" -Method Post -Body $notifPayload -Headers @{ "X-Requested-With" = "XMLHttpRequest" } -ContentType "application/json"
959
+ Write-Host "Dispatch confirmed." -ForegroundColor Green
960
+ } catch {
961
+ Write-Host "Notification dispatch failed." -ForegroundColor Yellow
962
+ }
963
+ }
964
+
965
+ function Send-AntigravityChat {
966
+ param([string]$Status, [string]$Report)
967
+ try {
968
+ Write-Host "[ANTIGRAVITY] Sending deployment report to chat..." -ForegroundColor Magenta
969
+ $emoji = if ($Status -eq "SUCCESS") { "βœ…" } else { "❌" }
970
+ $chatMessage = "$emoji Medizys Deployment $Status\`n\`n$Report"
971
+
972
+ # Append newline so the Antigravity Bridge auto-submits the message
973
+ $payload = @{ text = "$chatMessage\`n" } | ConvertTo-Json
974
+
975
+ Invoke-RestMethod \`
976
+ -Uri "http://localhost:5000/send_command" \`
977
+ -Method Post \`
978
+ -Body $payload \`
979
+ -ContentType "application/json" \`
980
+ -ErrorAction SilentlyContinue | Out-Null
981
+
982
+ Write-Host "[ANTIGRAVITY] Report dispatched to chat." -ForegroundColor Green
983
+ } catch {
984
+ Write-Host "[ANTIGRAVITY] Chat dispatch skipped (bridge not running)." -ForegroundColor DarkGray
985
+ }
986
+ }
987
+
988
+ function Invoke-ModernCommand {
989
+ param([string]$Step, [scriptblock]$Script, [switch]$NoExit)
990
+ Write-Host ""
991
+ Write-Host "[STEP] $($Step)..." -ForegroundColor Yellow
992
+ try {
993
+ # Execute script correctly
994
+ $global:LASTEXITCODE = 0
995
+ & $Script
996
+ $exitCode = $LASTEXITCODE
997
+
998
+ if ($exitCode -eq 0) {
999
+ Add-Step $Step "SUCCESS"
1000
+ Write-Host "[OK] $Step completed successfully." -ForegroundColor Green
1001
+ } else {
1002
+ throw "Command failed with exit code $exitCode."
1003
+ }
1004
+ } catch {
1005
+ $errorMsg = $_.ToString()
1006
+ Add-Step $Step "FAILED" "Critical Error: See Global Report"
1007
+ $GlobalReport.Error = "Step: $Step\`nError: $errorMsg"
1008
+
1009
+ if ($NoExit) {
1010
+ Write-Host "[WARN] $Step failed, but continuing (NoExit enabled)." -ForegroundColor Yellow
1011
+ throw $errorMsg
1012
+ }
1013
+
1014
+ $finalReport = Generate-FinalReport "FAILED"
1015
+
1016
+ # Send failure notification
1017
+ Send-Notification -Status "FAILED" -Report $finalReport
1018
+ Send-AntigravityChat -Status "FAILED" -Report $finalReport
1019
+
1020
+ Write-Host ""
1021
+ Write-Host "DEPLOYMENT FAILED!" -ForegroundColor Red
1022
+ Write-Host "-------------------------------------------------"
1023
+ Write-Host $errorMsg -ForegroundColor Gray
1024
+ Write-Host "-------------------------------------------------"
1025
+ Write-Host "[CLIPBOARD] Full error report copied to your clipboard!" -ForegroundColor Cyan
1026
+
1027
+ # Ensure we return to root before exiting
1028
+ Set-Location $PSScriptRoot
1029
+ exit 1
1030
+ }
1031
+ }
1032
+
1033
+ # Phase -1: Security Audits & Clean Install
1034
+ Invoke-ModernCommand "Lockfile Linting" { pnpm run lint:lockfile }
1035
+ Invoke-ModernCommand "Clean Installation (Strict Lockfile)" { pnpm install --frozen-lockfile }
1036
+
1037
+ # Phase 0: Pre-flight Sync
1038
+ Invoke-ModernCommand "Sync Frontend Routes" { node "$PSScriptRoot\\scripts\\sync-routes.js" }
1039
+ Invoke-ModernCommand "Sync Shared Types" { Copy-Item "$PSScriptRoot\\worker\\src\\shared-types.ts" "$PSScriptRoot\\frontend\\lib\\shared-types.ts" -Force }
1040
+ Invoke-ModernCommand "Worker ESLint Audit" { pnpm --filter mediqueue-worker run lint }
1041
+
1042
+ # Phase 1 & 2: Worker
1043
+ try {
1044
+ Push-Location worker
1045
+ # Database Schema Sync (using latest Drizzle patterns)
1046
+ if (!$SkipDBSync) {
1047
+ Invoke-ModernCommand "Database Enum Drift Audit" { pnpm run db:audit }
1048
+ Invoke-ModernCommand "Database Migrations (HTTP)" { pnpm run db:migrate }
1049
+ Invoke-ModernCommand "Worker Type Check" { pnpm run typecheck }
1050
+ } else {
1051
+ Write-Host "[SKIP] Database Schema Sync skipped." -ForegroundColor Gray
1052
+ Add-Step "Database Enum Drift Audit" "SKIPPED"
1053
+ Add-Step "Database Schema Sync" "SKIPPED"
1054
+ }
1055
+
1056
+ # Seed initial data (Achievements, etc.)
1057
+ if (!$SkipSeed) {
1058
+ Invoke-ModernCommand "Database Seeding" { pnpm run db:seed }
1059
+ } else {
1060
+ Write-Host "[SKIP] Database Seeding skipped." -ForegroundColor Gray
1061
+ Add-Step "Database Seeding" "SKIPPED"
1062
+ }
1063
+
1064
+ # Phase 2: Worker Deployment
1065
+ if ($SkipSentry) {
1066
+ Invoke-ModernCommand "Worker Deployment (Fast)" { pnpm run deploy:fast }
1067
+ } else {
1068
+ try {
1069
+ Invoke-ModernCommand "Worker Deployment (Sentry)" { pnpm run deploy:sentry } -NoExit
1070
+ } catch {
1071
+ Write-Host "[WARN] Sentry deployment failed (likely timeout or config). Falling back to fast deploy..." -ForegroundColor Yellow
1072
+ Invoke-ModernCommand "Worker Deployment (Fallback)" { pnpm run deploy:fast }
1073
+ }
1074
+ }
1075
+ } finally {
1076
+ Pop-Location
1077
+ }
1078
+
1079
+ # Phase 2.5: Router Worker Deployment
1080
+ if (!$SkipRouter) {
1081
+ try {
1082
+ Push-Location router-worker
1083
+ Invoke-ModernCommand "Router Worker Deployment" { npx wrangler@4.80.0 deploy }
1084
+ } finally {
1085
+ Pop-Location
1086
+ }
1087
+ } else {
1088
+ Write-Host "[SKIP] Router Worker Deployment skipped." -ForegroundColor Gray
1089
+ Add-Step "Router Worker Deployment" "SKIPPED"
1090
+ }
1091
+
1092
+ # Phase 3: Frontend Deployment
1093
+ if (!$SkipFrontend) {
1094
+
1095
+ try {
1096
+ Push-Location frontend
1097
+ $env:NODE_OPTIONS = "--max-old-space-size=8192"
1098
+ Write-Host "Cleaning previous build artifacts..." -ForegroundColor Gray
1099
+
1100
+ # PROACTIVE LOCK CLEANUP: Remove .next/lock if it exists to prevent build timeouts
1101
+ $lockFile = Join-Path (Get-Location) ".next\\lock"
1102
+ if (Test-Path $lockFile) {
1103
+ Write-Host "[RESILIENCE] Removing stale .next/lock..." -ForegroundColor Yellow
1104
+ Remove-Item -Force $lockFile -ErrorAction SilentlyContinue
1105
+ }
1106
+
1107
+ if (!$Fast) {
1108
+ Write-Host "Cleaning previous build artifacts (Full Mode)..." -ForegroundColor Gray
1109
+ if (Test-Path out) { Remove-Item -Recurse -Force out -ErrorAction SilentlyContinue }
1110
+ } else {
1111
+ Write-Host "Preserving build cache for faster iterations (Fast Mode)..." -ForegroundColor Cyan
1112
+ if (Test-Path out) { Remove-Item -Recurse -Force out -ErrorAction SilentlyContinue }
1113
+ }
1114
+
1115
+ # Generate Deployment Versioning for Awareness
1116
+ $deployId = (Get-Date).ToString("yyyyMMddHHmmss")
1117
+ $versionJson = @{
1118
+ version = "v2.5.2-stable"
1119
+ deployId = $deployId
1120
+ timestamp = (Get-Date).ToString("o")
1121
+ notes = "Portal boundaries and operational context audit."
1122
+ } | ConvertTo-Json
1123
+ $versionJson | Out-File -FilePath "public/version.json" -Encoding utf8
1124
+ Write-Host "[OK] Deployment version $deployId generated." -ForegroundColor Green
1125
+
1126
+ Invoke-ModernCommand "Frontend Type Check" { pnpm run typecheck }
1127
+
1128
+ if (!$SkipVisual) {
1129
+ Write-Host "\`n[STEP] Frontend Visual Tests..." -ForegroundColor Yellow
1130
+ $global:LASTEXITCODE = 0
1131
+ & pnpm run test:visual
1132
+ if ($global:LASTEXITCODE -eq 0) {
1133
+ Add-Step "Frontend Visual Tests" "SUCCESS"
1134
+ Write-Host "[OK] Frontend Visual Tests completed successfully." -ForegroundColor Green
1135
+ } else {
1136
+ Add-Step "Frontend Visual Tests" "FAILED (Ignored)"
1137
+ Write-Host "[WARN] Frontend Visual Tests failed, but continuing deployment as requested." -ForegroundColor Yellow
1138
+ $global:LASTEXITCODE = 0
1139
+ }
1140
+ } else {
1141
+ Write-Host "[SKIP] Frontend Visual Tests skipped." -ForegroundColor Gray
1142
+ Add-Step "Frontend Visual Tests" "SKIPPED"
1143
+ }
1144
+
1145
+ Invoke-ModernCommand "Frontend Build" { pnpm run build }
1146
+
1147
+ Write-Host "[RESILIENCE] Ensuring hidden public assets (e.g. .well-known) are copied to out..." -ForegroundColor Gray
1148
+ if (Test-Path "public\\.well-known") {
1149
+ if (!(Test-Path "out\\.well-known")) {
1150
+ New-Item -ItemType Directory -Path "out\\.well-known" | Out-Null
1151
+ }
1152
+ try {
1153
+ Copy-Item -Recurse -Force "public\\.well-known\\*" "out\\.well-known" -ErrorAction Stop
1154
+ } catch {
1155
+ Write-Host "[WARN] Could not copy .well-known: $($_.Exception.Message)" -ForegroundColor Yellow
1156
+ }
1157
+ Write-Host "[RESILIENCE] Copied .well-known to out directory." -ForegroundColor Green
1158
+ }
1159
+
1160
+ if (!(Test-Path "out\\index.html")) { New-Item -Path "out\\index.html" -ItemType File -Force | Out-Null }
1161
+ Invoke-ModernCommand "Mobile App Sync (Capacitor)" { pnpm exec cap sync }
1162
+ # Ensure bash + pnpm are resolvable for @cloudflare/next-on-pages (shellac dependency)
1163
+ $gitBashBin = "C:\\Program Files\\Git\\bin"
1164
+ $pnpmBin1 = "C:\\Program Files\\nodejs"
1165
+ $pnpmBin2 = "$env:LOCALAPPDATA\\pnpm"
1166
+ $originalPath = $env:PATH
1167
+ $env:PATH = "$gitBashBin;$pnpmBin1;$pnpmBin2;$env:PATH"
1168
+ Get-ChildItem -Path "out" -Recurse -Filter "__next*" -File | Remove-Item -Force -ErrorAction SilentlyContinue; Invoke-ModernCommand "Frontend Pages Upload" { pnpm run deploy }
1169
+ $env:PATH = $originalPath
1170
+ } finally {
1171
+ Pop-Location
1172
+ }
1173
+ } else {
1174
+ Write-Host "[SKIP] Frontend Deployment skipped." -ForegroundColor Gray
1175
+ Add-Step "Frontend Deployment" "SKIPPED"
1176
+ }
1177
+
1178
+ # Phase 3.5: Edge Verification
1179
+ if (!$SkipFrontend -and !$SkipRouter) {
1180
+ try {
1181
+ Push-Location frontend
1182
+
1183
+ # PROACTIVE PLAYWRIGHT RESILIENCE: Kill zombies and remove locks
1184
+ Write-Host "[RESILIENCE] Preparing clean state for Playwright..." -ForegroundColor Yellow
1185
+ $pwProcs = Get-WmiObject Win32_Process | Where-Object {
1186
+ $_.Name -match 'node.exe' -and
1187
+ ($_.CommandLine -match 'playwright' -or $_.CommandLine -match 'next dev')
1188
+ }
1189
+ foreach ($p in $pwProcs) {
1190
+ Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
1191
+ }
1192
+ $lockFile = Join-Path (Get-Location) ".next\\lock"
1193
+ if (Test-Path $lockFile) {
1194
+ Remove-Item -Force $lockFile -ErrorAction SilentlyContinue
1195
+ }
1196
+
1197
+ Invoke-ModernCommand "Edge Route Verification (Playwright)" { npx playwright test e2e/edge-router.spec.ts }
1198
+ } catch {
1199
+ Write-Host "[WARN] Edge Route Verification failed! Please check Cloudflare Worker logs." -ForegroundColor Red
1200
+ throw $_
1201
+ } finally {
1202
+ Pop-Location
1203
+ }
1204
+ } else {
1205
+ Write-Host "[SKIP] Edge Route Verification skipped (requires Frontend and Router sync)." -ForegroundColor Gray
1206
+ Add-Step "Edge Route Verification" "SKIPPED"
1207
+ }
1208
+
1209
+ # Phase 4: Mobile App Release
1210
+ if (!$SkipApp) {
1211
+ Invoke-ModernCommand "Mobile App Release" { & "$PSScriptRoot\\scripts\\release-mobile.ps1" }
1212
+ } else {
1213
+ Write-Host "[SKIP] Mobile App Release skipped." -ForegroundColor Gray
1214
+ Add-Step "Mobile App Release" "SKIPPED"
1215
+ }
1216
+
1217
+ # Phase 5: Finalize
1218
+ $finalReport = Generate-FinalReport "SUCCESS"
1219
+ Write-Host ""
1220
+ Write-Host "DEPLOYMENT SUCCESSFUL!" -ForegroundColor Green
1221
+
1222
+ # Telegram Deployment Notification
1223
+ Send-Notification -Status "SUCCESS" -Report $finalReport
1224
+
1225
+ # Antigravity Chat Notification
1226
+ Send-AntigravityChat -Status "SUCCESS" -Report $finalReport
1227
+
1228
+ Write-Host $finalReport
1229
+ Write-Host "[CLIPBOARD] Full report copied to your clipboard!" -ForegroundColor Cyan
1230
+ Write-Host ""
1231
+ Write-Host ">>> Deployment localized and verified." -ForegroundColor Cyan
1232
+
1233
+ `);
1234
+
1235
+ // 3. Agent Rules
1236
+ writeFile('AGENTS.md', `
1237
+ # High-Performance Engineering Standards
1238
+ 1. **Ponytail, lazy senior dev mode**: The best code is the code never written. YAGNI. Question complex requests, reuse existing standards, prefer deletion over addition, and write the minimal correct code.
1239
+ 2. **Edge-First Execution**: Backend logic must be optimized for serverless edge deployment (Cloudflare Workers).
1240
+ 3. **Data Layer Optimization**: Never execute untuned queries. Use connection pooling and batching.
1241
+ 4. **Minimalist Frontend Render**: Ensure frontend implementations are highly optimized for Next.js, favoring SSR where appropriate, avoiding heavy client bundles, and ensuring mobile-first responsive design.
1242
+ 5. **Master Debugging**: Trace issues to their root cause before writing a fix. Use the "Five Whys" protocol.
1243
+ 6. **Strict Typing**: TypeScript and Rust types must be explicit, avoid \`any\` or \`unwrap()\` where possible.
1244
+ 7. **Structured Logging**: Implement structured logging, avoiding excessive console.logs in production.
1245
+ 8. **Atomic Commits**: Ensure commits are small, atomic, and pass CI checks before pushing.
1246
+ 9. **Error Boundaries**: Frontend must wrap critical components in Error Boundaries to prevent full app crashes.
1247
+ 10. **No Placeholder Code**: Never write generic "TODO" or placeholder logic if the requirements are known.
1248
+ 11. **Security Best Practices**: Enforce strict input validation, rate limiting, and secure credential handling by default.
1249
+ 12. **Automated Testing & TDD**: Require unit and integration tests for critical paths. Code is not done until it's tested.
1250
+ 13. **Strict Linting & Formatting**: Enforce ESLint, Prettier, and Clippy with zero warnings allowed.
1251
+ 14. **Accessibility (a11y) First**: Require ARIA labels, semantic HTML, and keyboard navigation for all frontend components.
1252
+ 15. **Documentation Standards**: Mandate JSDoc (frontend) and Rustdoc (backend) for all public APIs and reusable components.
1253
+ 16. **CI/CD Pipeline Setup**: Ensure GitHub Actions (or similar) are configured for automated checks, tests, and deployments.
1254
+ 17. **State Management Rules**: Prefer local state or React Context; enforce strict rules against unnecessary global state.
1255
+ 18. **Environment Variable Validation**: Fail fast at startup if required \`.env\` variables are missing.
1256
+ 19. **Kill AI Slop**: Remove aesthetic clichΓ©s (indigo gradients, glowing cards, excessive emojis) that serve no functional purpose.
1257
+ 20. **Security First Principle**: Treat all external input as malicious; leverage the Security Review skill for all external-facing endpoints.
1258
+
1259
+ # Start Project Protocol
1260
+ When the user says "start project", you MUST immediately:
1261
+ 1. Ask the user for the name of the project and a brief description of what they want to build.
1262
+ 2. Wait for their response.
1263
+ 3. Once they respond, it is YOUR strict responsibility to initialize the standard project skeleton:
1264
+ a. Generate the Next.js frontend and Rust Cloudflare Worker backend in their respective directories (\`frontend/\` and \`backend/\`).
1265
+ b. Carefully modify the generated files to align with the project guidelines.
1266
+ c. **CRITICAL: Dev and Deploy Scripts**: The generated \`dev.ps1\`, \`dev.sh\`, \`deploy.ps1\`, and \`deploy.sh\` files are highly optimized custom runners (with clipboard integration, aggressive process cleanup, and error reporting). **Never** overwrite them with generic start/build scripts. If modifications are needed, surgically edit them to preserve their robust functionality.
1267
+ 4. Initialize a new git repository in the root directory (\`git init\`) and create an initial commit.
1268
+ 5. Notify the user that the project has been successfully scaffolded and all the lazy senior dev standards, security reviews, UI sniper setups, and AI slop removers are in place. Remind them to use \`./dev.ps1\` or \`./dev.sh\` once their \`frontend/\` and \`backend/\` directories are initialized.
1269
+ `);
1270
+
1271
+ // 4. Workflows
1272
+ writeFile('.agents/workflows/git-it.md', `
1273
+ # Safe Git Sync Workflow
1274
+ 1. Stage all changes (\`git add .\`).
1275
+ 2. Write a descriptive, conventional commit message based on the recent changes.
1276
+ 3. Commit the changes (\`git commit -m "..."\`).
1277
+ 4. Pull with rebase (\`git pull --rebase\`).
1278
+ 5. Push to the remote branch (\`git push\`).
1279
+ `);
1280
+
1281
+ writeFile('.agents/workflows/deploy-and-talk.md', `
1282
+ # Deploy and Talk Workflow
1283
+ 1. Execute the deployment script (\`./deploy.ps1\` or \`./deploy.sh\`).
1284
+ 2. Verify successful deployment output.
1285
+ 3. Send a Telegram notification using \`Invoke-RestMethod\` to alert the team that the deployment is complete. Include a brief changelog.
1286
+ `);
1287
+
1288
+ // 5. Skills
1289
+ writeFile('.agents/skills/ponytail/SKILL.md', `
1290
+ ---
1291
+ name: ponytail
1292
+ description: Enforces lazy senior dev principles. Efficient, not careless. Deletion over addition.
1293
+ ---
1294
+ # Ponytail, lazy senior dev mode
1295
+
1296
+ You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written.
1297
+
1298
+ Before writing any code, stop at the first rung that holds:
1299
+ 1. Does this need to be built at all? (YAGNI)
1300
+ 2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it.
1301
+ 3. Does the standard library already do this? Use it.
1302
+ 4. Does a native platform feature cover it? Use it.
1303
+ 5. Does an already-installed dependency solve it? Use it.
1304
+ 6. Can this be one line? Make it one line.
1305
+ 7. Only then: write the minimum code that works.
1306
+
1307
+ The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb.
1308
+
1309
+ Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once.
1310
+
1311
+ Rules:
1312
+ - No abstractions that weren't explicitly requested.
1313
+ - No new dependency if it can be avoided.
1314
+ - No boilerplate nobody asked for.
1315
+ - Deletion over addition. Boring over clever. Fewest files possible.
1316
+ - Shortest working diff wins, but only once you understand the problem.
1317
+ - Question complex requests: "Do you actually need X, or does Y cover it?"
1318
+ - Mark deliberate simplifications that cut a real corner with a known ceiling with a \`ponytail:\` comment naming the ceiling and upgrade path.
1319
+ `);
1320
+
1321
+ writeFile('.agents/skills/ui-sniper-telegram/SKILL.md', `
1322
+ ---
1323
+ name: ui-sniper-telegram
1324
+ description: Automates the setup of a UI Sniper system integrated with a Telegram bot for capturing frontend UI feedback directly to a Telegram group.
1325
+ ---
1326
+ # UI Sniper Telegram Integration Skill
1327
+ 1. **Ask for Credentials**: Prompt the user for their \`TELEGRAM_BOT_TOKEN\` and \`TELEGRAM_CHAT_ID\`. Wait for their response.
1328
+ 2. **Install**: Install \`ui-sniper\` and scaffold the Next.js API route (\`app/api/ui-sniper-webhook/route.ts\`), and set up the \`UISniperWrapper.tsx\` component.
1329
+ 3. **Configure**: Add the wrapper to \`app/layout.tsx\` and place the credentials in \`.env.local\`.
1330
+ `);
1331
+
1332
+ writeFile('.agents/skills/kill-ai-slop/SKILL.md', `
1333
+ ---
1334
+ name: kill-ai-slop
1335
+ description: Scans a web project to detect and strip out AI-generated aesthetic clichΓ©s (indigo gradients, excessive emojis, glowing cards) replacing them with clean, tasteful, and accessible design patterns.
1336
+ ---
1337
+
1338
+ # Kill AI Slop Skill
1339
+ Run the following command in the terminal to execute the scanner:
1340
+ \`\`\`bash
1341
+ npx --yes skills add yetone/kill-ai-slop
1342
+ \`\`\`
1343
+ Or execute the scanner directly on the project directory:
1344
+ \`\`\`bash
1345
+ npx --yes kill-ai-slop .
1346
+ \`\`\`
1347
+ Review the reported "tells" and apply the suggested clean fixes (e.g., removing indigo gradients, replacing emojis with proper SVG icons, removing glowing cards in favor of paper/ink aesthetics).
1348
+ `);
1349
+
1350
+ writeFile('.agents/skills/security-review/SKILL.md', `
1351
+ ---
1352
+ name: security-review
1353
+ description: 'AI-powered codebase security scanner that reasons about code like a security researcher β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.'
1354
+ ---
1355
+
1356
+ # Security Review
1357
+
1358
+ An AI-powered security scanner that reasons about your codebase the way a human security researcher would β€” tracing data flows, understanding component interactions, and catching vulnerabilities that pattern-matching tools miss.
1359
+
1360
+ ## Execution Workflow
1361
+ 1. **Scope Resolution**: Determine what to scan (specific path or entire project).
1362
+ 2. **Dependency Audit**: Audit dependencies first (check package.json, Cargo.toml for known vulnerable packages).
1363
+ 3. **Secrets & Exposure Scan**: Scan ALL files for hardcoded API keys, tokens, passwords, and \`.env\` files.
1364
+ 4. **Vulnerability Deep Scan**: Check for Injection Flaws, Authentication/Access Control issues, Data Handling weaknesses, and Cryptography issues.
1365
+ 5. **Cross-File Data Flow Analysis**: Trace user-controlled input from entry points to dangerous sinks.
1366
+ 6. **Self-Verification Pass**: Filter false positives.
1367
+ 7. **Generate Security Report**: Output a findings summary table and structured report with severities (CRITICAL, HIGH, MEDIUM, LOW, INFO).
1368
+ 8. **Propose Patches**: For every CRITICAL and HIGH finding, generate a concrete patch for human review. **Never auto-apply any patch.**
1369
+ `);
1370
+
1371
+ console.log('βœ… All skills, workflows, dev scripts, and rules generated successfully.');
1372
+ console.log('');
1373
+ console.log('πŸ“¦ Installing additional NPM packages in the current project...');
1374
+
1375
+ try {
1376
+ // If there is a package.json, install UI dependencies
1377
+ if (fs.existsSync('package.json')) {
1378
+ console.log(' - Installing ui-sniper and html2canvas...');
1379
+ execSync('npm install ui-sniper html2canvas', { stdio: 'inherit' });
1380
+ } else {
1381
+ console.log(' - No package.json found. Skipping ui-sniper installation.');
1382
+ }
1383
+ } catch (error) {
1384
+ console.warn('⚠️ Could not install npm dependencies automatically.');
1385
+ }
1386
+
1387
+ console.log('');
1388
+ console.log("\nπŸŽ‰ Scaffolding Complete! πŸŽ‰");
1389
+ console.log("πŸ‘‰ The workspace is fully loaded with Ponytail, Security Review, and Kill AI Slop skills.");
1390
+ console.log("πŸ‘‰ Go to your coding agent and say: 'start project' to begin! πŸš€");